Skip to main content

win_context_menu/
com.rs

1//! COM initialization guard and PIDL memory management.
2//!
3//! Windows Shell APIs require COM to be initialized on the calling thread in
4//! single-threaded apartment (STA) mode. The types in this module provide RAII
5//! wrappers so that COM lifetime and PIDL memory are automatically managed.
6
7use crate::error::{Error, Result};
8use windows::Win32::System::Com::{
9    CoInitializeEx, CoTaskMemFree, CoUninitialize, COINIT_APARTMENTTHREADED,
10    COINIT_DISABLE_OLE1DDE,
11};
12use windows::Win32::System::Ole::OleFlushClipboard;
13use windows::Win32::UI::Shell::Common::ITEMIDLIST;
14
15/// RAII guard for COM initialization. Calls `CoUninitialize` on drop.
16///
17/// # Threading
18///
19/// This guard initializes COM in **single-threaded apartment (STA)** mode,
20/// which is required by the Windows Shell context menu interfaces. All context
21/// menu operations must happen on the same thread that created the `ComGuard`.
22///
23/// `ComGuard` is intentionally **not `Send` or `Sync`** — moving it to another
24/// thread would violate STA rules.
25///
26/// # Example
27///
28/// ```no_run
29/// let _com = win_context_menu::init_com()?;
30/// // COM is active for the lifetime of `_com`
31/// # Ok::<(), win_context_menu::Error>(())
32/// ```
33pub struct ComGuard {
34    // prevent Send + Sync by holding a non-Send type
35    _not_send: std::marker::PhantomData<*mut ()>,
36}
37
38impl ComGuard {
39    /// Initialize COM in single-threaded apartment mode.
40    ///
41    /// Returns [`Error::ComInit`] if COM has already been initialized with an
42    /// incompatible threading model on this thread.
43    pub fn new() -> Result<Self> {
44        // SAFETY: `CoInitializeEx` is the documented way to enter the COM
45        // apartment. We pass `COINIT_APARTMENTTHREADED` for STA and
46        // `COINIT_DISABLE_OLE1DDE` to avoid legacy DDE issues. Calling this
47        // once per thread is safe; redundant calls return S_FALSE and are
48        // balanced by `CoUninitialize` in `Drop`.
49        unsafe {
50            CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
51                .ok()
52                .map_err(Error::ComInit)?;
53        }
54        Ok(Self {
55            _not_send: std::marker::PhantomData,
56        })
57    }
58}
59
60impl Drop for ComGuard {
61    fn drop(&mut self) {
62        // SAFETY: `OleFlushClipboard` converts any OLE clipboard data object
63        // into a static snapshot so that clipboard content (e.g. file copy)
64        // survives after this process exits. Safe to call even when no OLE
65        // clipboard data is present — it simply returns S_OK.
66        unsafe {
67            let _ = OleFlushClipboard();
68        }
69        // SAFETY: Balanced with the `CoInitializeEx` call in `new()`.
70        // Must run on the same thread that called `CoInitializeEx`.
71        unsafe {
72            CoUninitialize();
73        }
74    }
75}
76
77/// RAII wrapper for a PIDL allocated with `CoTaskMemAlloc`.
78///
79/// Automatically calls `CoTaskMemFree` on drop.
80pub(crate) struct Pidl {
81    ptr: *mut ITEMIDLIST,
82}
83
84impl Pidl {
85    /// Take ownership of a raw PIDL pointer.
86    ///
87    /// # Safety
88    ///
89    /// - `ptr` must have been allocated via `CoTaskMemAlloc` (e.g. returned by
90    ///   `SHParseDisplayName`), or be null.
91    /// - The caller must not free `ptr` after calling this function; `Pidl`
92    ///   takes ownership and will free it on drop.
93    pub unsafe fn from_raw(ptr: *mut ITEMIDLIST) -> Self {
94        Self { ptr }
95    }
96
97    /// Get the raw pointer without transferring ownership.
98    pub fn as_ptr(&self) -> *const ITEMIDLIST {
99        self.ptr as *const ITEMIDLIST
100    }
101}
102
103impl Drop for Pidl {
104    fn drop(&mut self) {
105        if !self.ptr.is_null() {
106            // SAFETY: `self.ptr` was allocated by `CoTaskMemAlloc` (guaranteed
107            // by the safety contract of `from_raw`). Freeing it once here is
108            // correct; the null-check prevents double-free.
109            unsafe {
110                CoTaskMemFree(Some(self.ptr as *const _));
111            }
112        }
113    }
114}