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