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 passing COINIT_MULTITHREADED to join MTA.
50 // SAFETY: Result is checked below, and CoUninitialize is guaranteed via Drop.
51 let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
52
53 if let Err(e) = hr.ok() {
54 tracing::error!(error = ?e, "COM MTA initialization failed");
55 return Err(anyhow::anyhow!("CoInitializeEx failed: {e}"));
56 }
57
58 tracing::debug!("COM MTA initialized");
59
60 Ok(Self {
61 _not_send: PhantomData,
62 })
63 }
64}
65
66impl Drop for ComGuard {
67 fn drop(&mut self) {
68 tracing::debug!("COM MTA teardown");
69 // SAFETY: Paired with the successful CoInitializeEx in new().
70 // SAFETY: Construction guarantees COM was initialized, so this call is always balanced. Only runs on the creating thread (!Send).
71 unsafe {
72 CoUninitialize();
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn com_guard_constructs_and_drops() {
83 // On Windows, CoInitializeEx(MTA) should succeed.
84 // On non-Windows CI, this test is skipped by target gate.
85 let guard = ComGuard::new();
86 assert!(guard.is_ok(), "ComGuard::new() should succeed: {guard:?}");
87 // Guard drops here — CoUninitialize runs.
88 }
89}