Skip to main content

opc_da_client/
com_guard.rs

1//! RAII guard for COM initialization/teardown.
2//!
3//! Ensures `CoUninitialize` is called exactly once per successful
4//! `CoInitializeEx`, even on early returns or panics.
5
6use std::marker::PhantomData;
7use windows::Win32::System::Com::{COINIT_MULTITHREADED, CoInitializeEx, CoUninitialize};
8
9/// Drop guard for COM thread initialization.
10///
11/// Calling [`ComGuard::new`] initializes COM in Multi-Threaded Apartment
12/// (MTA) mode. When the guard is dropped, `CoUninitialize` is called
13/// automatically.
14///
15/// # Thread Safety
16///
17/// `ComGuard` is intentionally `!Send` and `!Sync`. COM initialization
18/// is per-thread — the guard **must** be created and dropped on the same
19/// OS thread. This is enforced at compile time.
20///
21/// # Examples
22///
23/// ```no_run
24/// # use anyhow::Result;
25/// # use opc_da_client::ComGuard;
26/// # fn main() -> Result<()> {
27/// let _guard = ComGuard::new()?;
28/// // ... COM operations ...
29/// // CoUninitialize called automatically on drop
30/// # Ok(())
31/// # }
32/// ```
33#[derive(Debug)]
34pub struct ComGuard {
35    /// Prevents `Send + Sync` auto-derivation. COM init is per-thread.
36    _not_send: PhantomData<*mut ()>,
37}
38
39impl ComGuard {
40    /// Initialize COM in Multi-Threaded Apartment (MTA) mode.
41    ///
42    /// Returns `Ok(ComGuard)` on success (including `S_FALSE`, which
43    /// means COM was already initialized on this thread).
44    ///
45    /// # Errors
46    ///
47    /// Returns `Err` if `CoInitializeEx` fails with a fatal HRESULT.
48    pub fn new() -> anyhow::Result<Self> {
49        // SAFETY: `CoInitializeEx` is a standard Win32 FFI call.
50        // We pass `COINIT_MULTITHREADED` to join the MTA. The result
51        // is checked below, and `CoUninitialize` is guaranteed via Drop.
52        let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
53
54        if let Err(e) = hr.ok() {
55            tracing::error!(error = ?e, "COM MTA initialization failed");
56            return Err(anyhow::anyhow!("CoInitializeEx failed: {e}"));
57        }
58
59        tracing::debug!("COM MTA initialized");
60
61        Ok(Self {
62            _not_send: PhantomData,
63        })
64    }
65}
66
67impl Drop for ComGuard {
68    fn drop(&mut self) {
69        tracing::debug!("COM MTA teardown");
70        // SAFETY: Paired with the successful `CoInitializeEx` in `new()`.
71        // Construction guarantees COM was initialized, so this call is
72        // always balanced. Only runs on the creating thread (!Send).
73        unsafe {
74            CoUninitialize();
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn com_guard_constructs_and_drops() {
85        // On Windows, CoInitializeEx(MTA) should succeed.
86        // On non-Windows CI, this test is skipped by target gate.
87        let guard = ComGuard::new();
88        assert!(guard.is_ok(), "ComGuard::new() should succeed: {guard:?}");
89        // Guard drops here — CoUninitialize runs.
90    }
91}