Skip to main content

opc_da_client/opc_da/
com_utils.rs

1//! COM memory management and type conversion utilities for the OPC DA client.
2//!
3//! This module provides safe wrappers around COM memory allocations and arrays,
4//! as well as traits for converting between COM-native and Rust-native types.
5
6use windows::{
7    Win32::System::Com::{CoTaskMemAlloc, CoTaskMemFree},
8    core::PWSTR,
9};
10
11// ── Memory Management ───────────────────────────────────────────────
12
13/// A safe wrapper around arrays allocated by COM.
14///
15/// This struct ensures proper cleanup of COM-allocated memory when dropped.
16/// It provides safe access to the underlying array through slices.
17#[derive(Debug, Clone, PartialEq)]
18pub struct RemoteArray<T: Sized> {
19    pointer: RemotePointer<T>,
20    len: u32,
21}
22
23impl<T: Sized> RemoteArray<T> {
24    /// Creates a new `RemoteArray` with the specified length.
25    /// The underlying pointer is initialized to null.
26    #[inline(always)]
27    pub fn new(len: u32) -> Self {
28        Self {
29            pointer: RemotePointer::null(),
30            len,
31        }
32    }
33
34    /// Creates a `RemoteArray` from a raw pointer and length.
35    ///
36    /// # Safety
37    /// The caller must ensure that the pointer is valid and points to a COM-allocated array.
38    #[inline(always)]
39    pub(crate) fn from_mut_ptr(pointer: *mut T, len: u32) -> Self {
40        Self {
41            pointer: RemotePointer::from_raw(pointer),
42            len,
43        }
44    }
45
46    /// Creates a `RemoteArray` from a constant pointer and length.
47    ///
48    /// # Safety
49    /// The caller must ensure that the pointer is valid and points to a COM-allocated array.
50    #[inline(always)]
51    pub(crate) fn from_ptr(pointer: *const T, len: u32) -> Self {
52        Self {
53            pointer: RemotePointer::from_raw(pointer as *mut T),
54            len,
55        }
56    }
57
58    /// Creates an empty `RemoteArray`.
59    #[inline(always)]
60    pub fn empty() -> Self {
61        Self {
62            pointer: RemotePointer::null(),
63            len: 0,
64        }
65    }
66
67    /// Returns a mutable pointer to the array pointer.
68    ///
69    /// This is useful when calling COM functions that output an array via a pointer to a pointer.
70    #[inline(always)]
71    pub fn as_mut_ptr(&mut self) -> *mut *mut T {
72        self.pointer.as_mut_ptr()
73    }
74
75    /// Returns a slice to the underlying array.
76    ///
77    /// # Safety
78    /// The caller must ensure that the `pointer` is valid for reads and points to an array of `len` elements.
79    #[inline(always)]
80    pub fn as_slice(&self) -> &[T] {
81        if self.pointer.inner.is_null() || self.len == 0 {
82            return &[];
83        }
84
85        let len = usize::try_from(self.len).unwrap_or(0);
86
87        // SAFETY: Pointer and length are guaranteed to be valid for slice creation.
88        unsafe { core::slice::from_raw_parts(self.pointer.inner, len) }
89    }
90
91    /// Returns a mutable slice to the underlying array.
92    ///
93    /// # Safety
94    /// The caller must ensure that the `pointer` is valid for reads and writes and points to an array of `len` elements.
95    #[inline(always)]
96    pub fn as_mut_slice(&mut self) -> &mut [T] {
97        if self.pointer.inner.is_null() || self.len == 0 {
98            return &mut [];
99        }
100
101        let len = usize::try_from(self.len).unwrap_or(0);
102
103        // SAFETY: Pointer and length are guaranteed to be valid for mutable slice creation.
104        unsafe { core::slice::from_raw_parts_mut(self.pointer.inner, len) }
105    }
106
107    /// Returns the length of the array.
108    #[inline(always)]
109    pub fn len(&self) -> u32 {
110        if self.pointer.inner.is_null() {
111            return 0;
112        }
113
114        self.len
115    }
116
117    /// Checks if the array is empty.
118    #[inline(always)]
119    pub fn is_empty(&self) -> bool {
120        self.len == 0 || self.pointer.inner.is_null()
121    }
122
123    /// Returns a mutable pointer to the length.
124    ///
125    /// This is useful when calling COM functions that output the length via a pointer.
126    #[inline(always)]
127    pub fn as_mut_len_ptr(&mut self) -> *mut u32 {
128        &mut self.len
129    }
130
131    /// Sets the length of the array.
132    ///
133    /// # Safety
134    /// The caller must ensure that the new length is valid for the underlying array.
135    #[inline(always)]
136    pub(crate) unsafe fn set_len(&mut self, len: u32) {
137        self.len = len;
138    }
139
140    pub fn into_vec(self) -> Vec<RemotePointer<T>> {
141        self.as_slice()
142            .iter()
143            .map(|v| RemotePointer::from_raw(v as *const T as *mut T))
144            .collect()
145    }
146}
147
148impl<T: Sized> Default for RemoteArray<T> {
149    /// Creates an empty `RemoteArray` by default.
150    #[inline(always)]
151    fn default() -> Self {
152        Self::empty()
153    }
154}
155
156/// A safe wrapper around a pointer allocated by COM.
157///
158/// This struct ensures proper cleanup of COM-allocated memory when dropped.
159/// It provides methods to access the underlying pointer.
160#[repr(transparent)]
161#[derive(Debug, Clone, PartialEq)]
162pub struct RemotePointer<T: Sized> {
163    inner: *mut T,
164}
165
166impl<T: Sized> RemotePointer<T> {
167    /// Creates a new `RemotePointer` initialized to null.
168    #[inline(always)]
169    pub fn null() -> Self {
170        Self {
171            inner: core::ptr::null_mut(),
172        }
173    }
174
175    /// Returns a mutable pointer to the inner pointer.
176    ///
177    /// Useful for COM functions that output data via a pointer to a pointer.
178    #[inline(always)]
179    pub(crate) fn from_raw(pointer: *mut T) -> Self {
180        Self { inner: pointer }
181    }
182
183    pub(crate) fn copy_slice(value: &[T]) -> Self {
184        // SAFETY: Allocates memory for slice using COM CoTaskMemAlloc.
185        let pointer = unsafe { CoTaskMemAlloc(core::mem::size_of_val(value)) };
186        // SAFETY: Destination buffer was allocated with sufficient capacity and pointers are non-overlapping.
187        unsafe {
188            core::ptr::copy_nonoverlapping(value.as_ptr(), pointer as _, value.len());
189        }
190        Self {
191            inner: pointer as _,
192        }
193    }
194
195    #[inline(always)]
196    pub fn as_mut_ptr(&mut self) -> *mut *mut T {
197        &mut self.inner
198    }
199
200    /// Returns an `Option` referencing the inner value if it is not null.
201    ///
202    /// # Safety
203    /// The caller must ensure that the inner pointer is valid for reads.
204    #[inline(always)]
205    pub fn as_ref(&self) -> Option<&T> {
206        // SAFETY: Converting raw pointer to reference after validating pointer safety.
207        unsafe { self.inner.as_ref() }
208    }
209
210    #[inline(always)]
211    pub fn ok(&self) -> windows::core::Result<&T> {
212        // SAFETY: Converting raw pointer to reference after validating pointer safety.
213        unsafe { self.inner.as_ref() }.ok_or_else(|| {
214            windows::core::Error::new(windows::Win32::Foundation::E_POINTER, "Pointer is null")
215        })
216    }
217
218    #[inline(always)]
219    pub fn from_option<R: Into<RemotePointer<T>>>(value: Option<R>) -> Self {
220        match value {
221            Some(value) => value.into(),
222            None => Self::null(),
223        }
224    }
225}
226
227impl<T: Sized> Default for RemotePointer<T> {
228    /// Creates a new `RemotePointer` initialized to null by default.
229    #[inline(always)]
230    fn default() -> Self {
231        Self::null()
232    }
233}
234
235impl From<PWSTR> for RemotePointer<u16> {
236    /// Converts a `PWSTR` to a `RemotePointer<u16>`.
237    #[inline(always)]
238    fn from(value: PWSTR) -> Self {
239        Self {
240            inner: value.as_ptr(),
241        }
242    }
243}
244
245impl From<&str> for RemotePointer<u16> {
246    /// Converts a string slice to a `RemotePointer<u16>`.
247    #[inline(always)]
248    fn from(value: &str) -> Self {
249        Self::copy_slice(&value.encode_utf16().chain(Some(0)).collect::<Vec<u16>>())
250    }
251}
252
253impl TryFrom<RemotePointer<u16>> for String {
254    type Error = windows::core::Error;
255
256    /// Attempts to convert a `RemotePointer<u16>` to a `String`.
257    ///
258    /// # Errors
259    /// Returns an error if the pointer is null or if the string conversion fails.
260    #[inline(always)]
261    fn try_from(value: RemotePointer<u16>) -> Result<Self, Self::Error> {
262        if value.inner.is_null() {
263            return Err(windows::Win32::Foundation::E_POINTER.into());
264        }
265
266        // SAFETY: Has checked for non-null pointer above.
267        Ok(unsafe { PWSTR(value.inner).to_string() }?)
268    }
269}
270
271impl TryFrom<RemotePointer<u16>> for Option<String> {
272    type Error = windows::core::Error;
273
274    /// Attempts to convert a `RemotePointer<u16>` to an `Option<String>`.
275    ///
276    /// # Errors
277    /// Returns an error if the string conversion fails.
278    #[inline(always)]
279    fn try_from(value: RemotePointer<u16>) -> Result<Self, Self::Error> {
280        if value.inner.is_null() {
281            return Ok(None);
282        }
283
284        // SAFETY: Has checked for non-null pointer above.
285        Ok(Some(unsafe { PWSTR(value.inner).to_string() }?))
286    }
287}
288
289impl RemotePointer<u16> {
290    /// Returns a mutable pointer to a `PWSTR`.
291    #[inline(always)]
292    pub fn as_mut_pwstr_ptr(&mut self) -> *mut PWSTR {
293        &mut self.inner as *mut *mut u16 as *mut PWSTR
294    }
295}
296
297impl<T: Sized> Drop for RemotePointer<T> {
298    /// Drops the `RemotePointer`, freeing the COM-allocated memory.
299    #[inline(always)]
300    fn drop(&mut self) {
301        if !self.inner.is_null() {
302            // SAFETY: Memory was allocated via COM CoTaskMemAlloc and pointer is non-null.
303            unsafe {
304                CoTaskMemFree(Some(self.inner as _));
305            }
306        }
307    }
308}
309
310/// A safe wrapper around locally allocated memory needing to be passed to COM functions.
311///
312/// This struct is useful for preparing data to be read by COM functions.
313pub struct LocalPointer<T: Sized> {
314    inner: Option<Box<T>>,
315}
316
317impl<T: Sized> LocalPointer<T> {
318    /// Creates a new `LocalPointer` from an optional value.
319    #[inline(always)]
320    pub fn new(value: Option<T>) -> Self {
321        Self {
322            inner: value.map(Box::new),
323        }
324    }
325
326    /// Creates a `LocalPointer` from a boxed value.
327    #[inline(always)]
328    pub fn from_box(value: Box<T>) -> Self {
329        Self { inner: Some(value) }
330    }
331
332    #[inline(always)]
333    pub fn from_option<R: Into<LocalPointer<T>>>(value: Option<R>) -> Self {
334        match value {
335            Some(value) => value.into(),
336            None => Self::new(None),
337        }
338    }
339
340    /// Returns a constant pointer to the inner value.
341    #[inline(always)]
342    pub fn as_ptr(&self) -> *const T {
343        match &self.inner {
344            Some(value) => value.as_ref() as *const T,
345            None => std::ptr::null_mut(),
346        }
347    }
348
349    /// Returns a mutable pointer to the inner value.
350    #[inline(always)]
351    pub fn as_mut_ptr(&mut self) -> *mut T {
352        match &mut self.inner {
353            Some(value) => value.as_mut() as *mut T,
354            None => std::ptr::null_mut(),
355        }
356    }
357
358    /// Consumes the `LocalPointer`, returning the inner value if it exists.
359    #[inline(always)]
360    pub fn into_inner(self) -> Option<T> {
361        self.inner.map(|v| *v)
362    }
363
364    /// Returns a reference to the inner value if it exists.
365    #[inline(always)]
366    pub fn inner(&self) -> Option<&T> {
367        self.inner.as_ref().map(|v| v.as_ref())
368    }
369}
370
371// Implementations for string handling
372
373impl<S: AsRef<str>> From<S> for LocalPointer<Vec<u16>> {
374    /// Converts a string slice to a `LocalPointer` containing a UTF-16 encoded null-terminated string.
375    #[inline(always)]
376    fn from(s: S) -> Self {
377        Self::new(Some(s.as_ref().encode_utf16().chain(Some(0)).collect()))
378    }
379}
380
381impl From<&[String]> for LocalPointer<Vec<Vec<u16>>> {
382    /// Converts a slice of `String`s to a `LocalPointer` containing vectors of UTF-16 encoded null-terminated strings.
383    #[inline(always)]
384    fn from(values: &[String]) -> Self {
385        Self::new(Some(
386            values
387                .iter()
388                .map(|s| s.encode_utf16().chain(Some(0)).collect())
389                .collect(),
390        ))
391    }
392}
393
394impl<T> LocalPointer<Vec<T>> {
395    /// Returns the length of the inner vector.
396    #[inline(always)]
397    pub fn len(&self) -> usize {
398        match &self.inner {
399            Some(values) => values.len(),
400            None => 0,
401        }
402    }
403
404    /// Checks if the inner vector is empty.
405    #[inline(always)]
406    pub fn is_empty(&self) -> bool {
407        match &self.inner {
408            Some(values) => values.is_empty(),
409            None => true,
410        }
411    }
412
413    /// Returns a constant pointer to the inner array.
414    #[inline(always)]
415    pub fn as_array_ptr(&self) -> *const T {
416        match &self.inner {
417            Some(values) => values.as_ptr(),
418            None => std::ptr::null(),
419        }
420    }
421
422    /// Returns a mutable pointer to the inner array.
423    #[inline(always)]
424    pub fn as_mut_array_ptr(&mut self) -> *mut T {
425        match &mut self.inner {
426            Some(values) => values.as_mut_ptr(),
427            None => std::ptr::null_mut(),
428        }
429    }
430}
431
432impl LocalPointer<Vec<Vec<u16>>> {
433    /// Converts the inner vector of UTF-16 strings to a vector of `PWSTR`.
434    #[inline(always)]
435    pub fn as_pwstr_array(&self) -> Vec<windows::core::PWSTR> {
436        match &self.inner {
437            Some(values) => values
438                .iter()
439                .map(|value| windows::core::PWSTR(value.as_ptr() as _))
440                .collect(),
441            None => vec![windows::core::PWSTR::null()],
442        }
443    }
444
445    /// Converts the inner vector of UTF-16 strings to a vector of `PCWSTR`.
446    #[inline(always)]
447    pub fn as_pcwstr_array(&self) -> Vec<windows::core::PCWSTR> {
448        match &self.inner {
449            Some(values) => values
450                .iter()
451                .map(|value| windows::core::PCWSTR::from_raw(value.as_ptr() as _))
452                .collect(),
453            None => vec![windows::core::PCWSTR::null()],
454        }
455    }
456}
457
458impl LocalPointer<Vec<u16>> {
459    /// Converts the inner UTF-16 string to a `PWSTR`.
460    #[inline(always)]
461    pub fn as_pwstr(&self) -> windows::core::PWSTR {
462        match &self.inner {
463            Some(value) => windows::core::PWSTR(value.as_ptr() as _),
464            None => windows::core::PWSTR::null(),
465        }
466    }
467
468    /// Converts the inner UTF-16 string to a `PCWSTR`.
469    #[inline(always)]
470    pub fn as_pcwstr(&self) -> windows::core::PCWSTR {
471        match &self.inner {
472            Some(value) => windows::core::PCWSTR::from_raw(value.as_ptr() as _),
473            None => windows::core::PCWSTR::null(),
474        }
475    }
476}
477
478// ── Native Conversion Traits ────────────────────────────────────────
479
480pub(crate) trait IntoBridge<Bridge> {
481    fn into_bridge(self) -> Bridge;
482}
483
484pub(crate) trait ToNative<Native> {
485    fn to_native(&self) -> Native;
486}
487
488pub(crate) trait FromNative<Native> {
489    fn from_native(native: &Native) -> Self
490    where
491        Self: Sized;
492}
493
494pub(crate) trait TryToNative<Native> {
495    fn try_to_native(&self) -> windows::core::Result<Native>;
496}
497
498pub(crate) trait TryFromNative<Native> {
499    fn try_from_native(native: &Native) -> windows::core::Result<Self>
500    where
501        Self: Sized;
502}
503
504pub(crate) trait TryToLocal<Local> {
505    fn try_to_local(&self) -> windows::core::Result<Local>;
506}
507
508impl<Native, T: TryFromNative<Native>> TryToLocal<T> for Native {
509    fn try_to_local(&self) -> windows::core::Result<T> {
510        T::try_from_native(self)
511    }
512}
513
514impl<Native, T: FromNative<Native>> TryFromNative<Native> for T {
515    fn try_from_native(native: &Native) -> windows::core::Result<Self> {
516        Ok(Self::from_native(native))
517    }
518}
519
520impl<Native, T: ToNative<Native>> TryToNative<Native> for T {
521    fn try_to_native(&self) -> windows::core::Result<Native> {
522        Ok(self.to_native())
523    }
524}
525
526impl<Bridge, B: IntoBridge<Bridge>> IntoBridge<Vec<Bridge>> for Vec<B> {
527    fn into_bridge(self) -> Vec<Bridge> {
528        self.into_iter().map(IntoBridge::into_bridge).collect()
529    }
530}
531
532impl<Bridge, B: IntoBridge<Bridge> + Clone> IntoBridge<Vec<Bridge>> for &[B] {
533    fn into_bridge(self) -> Vec<Bridge> {
534        self.iter().cloned().map(IntoBridge::into_bridge).collect()
535    }
536}
537
538impl<Native, T: TryToNative<Native>> TryToNative<Vec<Native>> for Vec<T> {
539    fn try_to_native(&self) -> windows::core::Result<Vec<Native>> {
540        self.iter().map(TryToNative::try_to_native).collect()
541    }
542}
543
544impl TryFromNative<RemoteArray<windows::core::HRESULT>> for Vec<windows::core::Result<()>> {
545    fn try_from_native(
546        native: &RemoteArray<windows::core::HRESULT>,
547    ) -> windows::core::Result<Self> {
548        Ok(native.as_slice().iter().map(|v| (*v).ok()).collect())
549    }
550}
551
552impl<Native, T: TryFromNative<Native>> TryFromNative<RemoteArray<Native>> for Vec<T> {
553    fn try_from_native(native: &RemoteArray<Native>) -> windows::core::Result<Self> {
554        native.as_slice().iter().map(T::try_from_native).collect()
555    }
556}
557
558impl<Native, T: TryFromNative<Native>>
559    TryFromNative<(RemoteArray<Native>, RemoteArray<windows::core::HRESULT>)>
560    for Vec<windows::core::Result<T>>
561{
562    fn try_from_native(
563        native: &(RemoteArray<Native>, RemoteArray<windows::core::HRESULT>),
564    ) -> windows::core::Result<Self> {
565        let (results, errors) = native;
566        if results.len() != errors.len() {
567            return Err(windows::core::Error::new(
568                windows::Win32::Foundation::E_INVALIDARG,
569                "Results and errors arrays have different lengths",
570            ));
571        }
572
573        Ok(results
574            .as_slice()
575            .iter()
576            .zip(errors.as_slice())
577            .map(|(result, error)| {
578                if error.is_ok() {
579                    T::try_from_native(result)
580                } else {
581                    Err((*error).into())
582                }
583            })
584            .collect())
585    }
586}
587
588impl TryFromNative<windows::Win32::Foundation::FILETIME> for std::time::SystemTime {
589    fn try_from_native(
590        native: &windows::Win32::Foundation::FILETIME,
591    ) -> windows::core::Result<Self> {
592        let ft = ((native.dwHighDateTime as u64) << 32) | (u64::from(native.dwLowDateTime));
593        let duration_since_1601 = std::time::Duration::from_nanos(ft * 100);
594
595        let windows_to_unix_epoch_diff = std::time::Duration::from_secs(11_644_473_600);
596        let duration_since_unix_epoch = duration_since_1601
597            .checked_sub(windows_to_unix_epoch_diff)
598            .ok_or_else(|| {
599                windows::core::Error::new(
600                    windows::Win32::Foundation::E_INVALIDARG,
601                    "FILETIME is before UNIX_EPOCH",
602                )
603            })?;
604
605        Ok(std::time::UNIX_EPOCH + duration_since_unix_epoch)
606    }
607}
608
609#[macro_export]
610/// Helper macro for instantiating native COM structs from safe types.
611macro_rules! try_from_native {
612    ($native:expr) => {
613        $crate::opc_da::com_utils::TryFromNative::try_from_native($native)?
614    };
615}
616
617impl TryToNative<windows::Win32::Foundation::FILETIME> for std::time::SystemTime {
618    fn try_to_native(&self) -> windows::core::Result<windows::Win32::Foundation::FILETIME> {
619        let duration_since_unix_epoch =
620            self.duration_since(std::time::UNIX_EPOCH).map_err(|_| {
621                windows::core::Error::new(
622                    windows::Win32::Foundation::E_INVALIDARG,
623                    "SystemTime is before UNIX_EPOCH",
624                )
625            })?;
626
627        let duration_since_windows_epoch =
628            duration_since_unix_epoch + std::time::Duration::from_secs(11_644_473_600);
629
630        let ft = duration_since_windows_epoch.as_nanos() / 100;
631
632        Ok(windows::Win32::Foundation::FILETIME {
633            dwLowDateTime: ft as u32,
634            dwHighDateTime: (ft >> 32) as u32,
635        })
636    }
637}
638
639impl TryFromNative<windows::core::PWSTR> for String {
640    fn try_from_native(native: &windows::core::PWSTR) -> windows::core::Result<Self> {
641        RemotePointer::from(*native).try_into()
642    }
643}