intercom 0.4.0

Utilities for writing COM visible Rust components.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
use super::*;
use crate::attributes::{ComClass, ComInterface, HasInterface};
use crate::raw::RawComPtr;
use crate::type_system::TypeSystemName;
use std::sync::atomic::{AtomicU32, Ordering};

/// Pointer to a COM-enabled Rust struct.
///
/// Intercom requires a specific memory layout for the COM objects so that it
/// can implement reference counting and map COM method calls back to the
/// target struct instance.
///
/// This is done by requiring each COM-enabled Rust object is constructed
/// through a `ComBox<T>` type.
///
/// Technically the memory layout is specified by the [`ComBoxData`](struct.ComBoxData.html)
/// type, however that type shouldn't be needed by the user. For all intents
/// the `ComBox` type is _the_ COM-compatible object handle.
pub struct ComBox<T: ComClass>
{
    data: *mut ComBoxData<T>,
}

impl<T: ComClass> ComBox<T>
{
    /// Constructs a new `ComBox` by placing the `value` into reference
    /// counted memory.
    ///
    /// - `value` - The initial state to use for the COM object.
    pub fn new(value: T) -> ComBox<T>
    {
        // Construct a ComBoxData in memory and track the reference on it.
        let cb = ComBoxData::new(value);
        unsafe { ComBoxData::add_ref(&*cb) };

        // Return the struct.
        ComBox { data: cb }
    }

    /// Acquires a ComItf for this struct.
    ///
    /// # Safety
    ///
    /// The ComItf must not outlive the current instance without invoking
    /// `add_ref`.
    unsafe fn as_comitf<I: ComInterface + ?Sized>(&self) -> ComItf<I>
    where
        T: HasInterface<I>,
    {
        let (automation_ptr, raw_ptr) = {
            let vtbl = &self.as_ref().vtable_list;

            let automation_ptr = match I::iid(TypeSystemName::Automation) {
                Some(iid) => match <T as ComClass>::query_interface(vtbl, iid) {
                    Ok(itf) => itf,
                    Err(_) => ::std::ptr::null_mut(),
                },
                None => ::std::ptr::null_mut(),
            };

            let raw_ptr = match I::iid(TypeSystemName::Raw) {
                Some(iid) => match <T as ComClass>::query_interface(vtbl, iid) {
                    Ok(itf) => itf,
                    Err(_) => ::std::ptr::null_mut(),
                },
                None => ::std::ptr::null_mut(),
            };

            (automation_ptr, raw_ptr)
        };

        ComItf::maybe_new(
            raw::InterfacePtr::new(automation_ptr),
            raw::InterfacePtr::new(raw_ptr),
        )
        .expect("Intercom failed to create interface pointers")
    }
}

impl<T: ComClass + std::fmt::Debug> std::fmt::Debug for ComBox<T>
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
    {
        write!(f, "ComBox(")?;
        self.as_ref().fmt(f)?;
        write!(f, ")")
    }
}

impl<T: ComClass> Drop for ComBox<T>
{
    /// Decreases the reference count by one. If this is the last reference
    /// the memory will be deallocated.
    fn drop(&mut self)
    {
        unsafe { ComBoxData::release(self.data) };
    }
}

impl<T: ComClass> AsMut<ComBoxData<T>> for ComBox<T>
{
    fn as_mut(&mut self) -> &mut ComBoxData<T>
    {
        // 'data' should always be valid pointer.
        unsafe { self.data.as_mut().expect("ComBox had null reference") }
    }
}

impl<T: ComClass> AsRef<ComBoxData<T>> for ComBox<T>
{
    fn as_ref(&self) -> &ComBoxData<T>
    {
        // 'data' should always be valid pointer.
        unsafe { self.data.as_ref().expect("ComBox had null reference") }
    }
}

impl<I: ComInterface + ?Sized, T: HasInterface<I>> From<ComBox<T>> for ComRc<I>
{
    fn from(source: ComBox<T>) -> ComRc<I>
    {
        // as_comitf does not change the reference count so attach is safe
        // as long as we forget the source so it won't release it either.
        unsafe {
            let rc = ComRc::attach(source.as_comitf());
            std::mem::forget(source);
            rc
        }
    }
}

impl<I: ComInterface + ?Sized, T: HasInterface<I>> From<&ComBox<T>> for ComRc<I>
{
    fn from(combox: &ComBox<T>) -> Self
    {
        // The ComItf temporary doesn't outlive self, making this safe.
        unsafe { ComRc::from(&combox.as_comitf()) }
    }
}

/// Type factory for the concrete COM coclass types.
///
/// Includes the virtual tables required for COM method invocations, reference
/// count required for `IUnknown` implementation and the custom value struct
/// required for any user defined interfaces.
///
/// While this struct is available for manual handling of raw COM interface
/// pointers, it's worth realizing that it's use is inherently unsafe. Most of
/// the methods implemented for the type come with conditions that Rust isn't
/// able to enforce.
///
/// The methods that handle `RawComPtr` types must only be invoked with correct
/// pointer values. There's no type checking for the pointers and the `ComBoxData`
/// will make serious assumptions on the pointers passed in.
///
/// Furthermore the `new_ptr` constructor and the `IUnknown` methods `add_ref`
/// and `release` must be used correctly together. Failure to do so will result
/// either in memory leaks or access to dangling pointers.
#[repr(C)]
pub struct ComBoxData<T: ComClass>
{
    vtable_list: T::VTableList,
    ref_count: AtomicU32,
    value: T,
}

impl<T: ComClass> ComBoxData<T>
{
    /// Creates a new ComBoxData and returns a pointer to it.
    ///
    /// The box is initialized with a reference count of zero. In most cases
    /// the ComBoxData creation is followed by query_interface, which increments the
    /// ref_count.
    ///
    /// The value should be cleaned by calling 'release'.
    pub fn new(value: T) -> *mut ComBoxData<T>
    {
        // TODO: Fix this to use raw heap allocation at some point. There's
        // no need to construct and immediately detach a Box.
        Box::into_raw(Box::new(ComBoxData {
            vtable_list: T::VTABLE,
            ref_count: AtomicU32::new(0),
            value,
        }))
    }

    /// Acquires a specific interface pointer.
    ///
    /// Increments the reference count to include the reference through the
    /// returned interface pointer.
    ///
    /// The acquired interface must be released explicitly when not needed
    /// anymore.
    ///
    /// # Safety
    ///
    /// The `out` pointer must be valid for writing the interface pointer to.
    pub unsafe fn query_interface(this: &Self, riid: REFIID, out: *mut RawComPtr) -> raw::HRESULT
    {
        match T::query_interface(&this.vtable_list, riid) {
            Ok(ptr) => {
                *out = ptr;
                Self::add_ref(this);
                raw::S_OK
            }
            Err(e) => {
                *out = std::ptr::null_mut();
                e
            }
        }
    }

    /// Increments the reference count.
    ///
    /// Returns the reference count after the increment.
    ///
    /// # Safety
    ///
    /// The method isn't technically unsafe in regard to Rust unsafety, but
    /// it's marked as unsafe to discourage it's use due to high risks of
    /// memory leaks.
    pub unsafe fn add_ref(this: &Self) -> u32
    {
        let previous_value = this.ref_count.fetch_add(1, Ordering::Relaxed);
        previous_value + 1
    }

    /// Gets the reference count of the object.
    pub fn get_ref_count(&self) -> u32
    {
        self.ref_count.load(Ordering::Relaxed)
    }

    /// Decrements the reference count. Destroys the object if the count reaches
    /// zero.
    ///
    /// Returns the reference count after the release.
    ///
    /// # Safety
    ///
    /// The pointer must be valid and not previously released. After the call
    /// completes, the struct may have been deallocated and the pointer should
    /// be considered dangling.
    pub unsafe fn release(this: *mut Self) -> u32
    {
        // Ensure we're not releasing an interface that has no references.
        //
        // Note: If the interface has no references, it has already been
        // dropped. As a result we can't guarantee that it's ref_count stays
        // as zero as the memory could have been reallocated for something else.
        //
        // However this is still an effective check in the case where the client
        // attempts to release a com pointer twice and the memory hasn't been
        // reused.
        //
        // It might not be deterministic, but in the cases where it triggers
        // it's way better than the access violation error we'd otherwise get.
        if (*this).ref_count.load(Ordering::Relaxed) == 0 {
            panic!("Attempt to release pointer with no references.");
        }

        // Decrease the ref count and store a copy of it. We'll need a local
        // copy for a return value in case we end up dropping the ComBoxData
        // instance. after the drop referencing *this would be undeterministic.
        let previous_value = (*this).ref_count.fetch_sub(1, Ordering::Relaxed);
        let rc = previous_value - 1;

        // If that was the last reference we can drop self. Do this by giving
        // it back to a box and then dropping the box. This should reverse the
        // allocation we did by boxing the value in the first place.
        if rc == 0 {
            drop(Box::from_raw(this));
        }
        rc
    }

    /// Converts a RawComPtr to a ComBoxData reference.
    ///
    /// # Safety
    ///
    /// The method is unsafe in two different ways:
    ///
    /// - There is no way for the method to ensure the RawComPtr points to
    ///   a valid ComBoxData<T> instance. It's the caller's responsibility to
    ///   ensure the method is not called with invalid pointers.
    ///
    /// - As the pointers have no lifetime tied to them, the borrow checker
    ///   is unable to enforce the lifetime of the ComBoxData reference. If the
    ///   ComBoxData is free'd by calling release on the pointer, the ComBoxData
    ///   reference will still reference the old, now free'd value. The caller
    ///   must ensure that the returned reference won't be used in case the
    ///   ComBoxData is released.
    pub unsafe fn from_ptr<'a>(ptr: RawComPtr) -> &'a mut ComBoxData<T>
    {
        &mut *(ptr as *mut ComBoxData<T>)
    }

    /// Returns a reference to the virtual table on the ComBoxData.
    pub fn vtable(ct: &ComBox<T>) -> &T::VTableList
    {
        unsafe { &(*ct.data).vtable_list }
    }

    /// Gets the ComBoxData holding the value.
    ///
    /// # Safety
    ///
    /// This is unsafe for two reasons:
    ///
    /// - There is no way for the method to check that the value is actually
    ///   contained in a `ComBoxData`. It is up to the caller to ensure this method
    ///   is only called with values that exist within a `ComBoxData`.
    ///
    /// - The method returns a mutable reference to the ComBoxData containing the
    ///   value. As demonstrated by the parameter type, the caller already has
    ///   a mutable reference to the value itself. As a result the caller will
    ///   end up with two different mutable references to the value - the direct
    ///   one given as a parameter and an indirect one available through the
    ///   return value. The caller should not attempt to access the value data
    ///   through the returned `ComBoxData` reference.
    pub unsafe fn of(value: &T) -> &ComBoxData<T>
    {
        // Resolve the offset of the 'value' field.
        let null_combox = std::ptr::null() as *const ComBoxData<T>;
        let value_offset = &((*null_combox).value) as *const _ as usize;

        let combox_loc = value as *const T as usize - value_offset;
        &mut *(combox_loc as *mut ComBoxData<T>)
    }

    /// Gets the ComBoxData holding the value.
    ///
    /// # Safety
    ///
    /// This is unsafe for two reasons:
    ///
    /// - There is no way for the method to check that the value is actually
    ///   contained in a `ComBoxData`. It is up to the caller to ensure this method
    ///   is only called with values that exist within a `ComBoxData`.
    ///
    /// - The method returns a mutable reference to the ComBoxData containing the
    ///   value. As demonstrated by the parameter type, the caller already has
    ///   a mutable reference to the value itself. As a result the caller will
    ///   end up with two different mutable references to the value - the direct
    ///   one given as a parameter and an indirect one available through the
    ///   return value. The caller should not attempt to access the value data
    ///   through the returned `ComBoxData` reference.
    pub unsafe fn of_mut(value: &mut T) -> &mut ComBoxData<T>
    {
        // Resolve the offset of the 'value' field.
        let null_combox = std::ptr::null() as *const ComBoxData<T>;
        let value_offset = &((*null_combox).value) as *const _ as usize;

        let combox_loc = value as *mut T as usize - value_offset;
        &mut *(combox_loc as *mut ComBoxData<T>)
    }

    /// Returns a reference to a null-ComBoxData vtable pointer list.
    ///
    /// # Safety
    ///
    /// **The reference itself is invalid and must not be dereferenced.**
    ///
    /// The reference may be used to further get references to the various
    /// VTableList fields to resolve offset values between the various VTable
    /// pointers and the actual `ComBoxData` containing these pointers.
    #[inline]
    pub unsafe fn null_vtable() -> &'static T::VTableList
    {
        let null_combox = std::ptr::null() as *const ComBoxData<T>;
        &(*null_combox).vtable_list
    }
}

impl<T> std::ops::Deref for ComBoxData<T>
where
    T: ComClass,
{
    type Target = T;
    fn deref(&self) -> &T
    {
        &self.value
    }
}

impl<T> std::ops::DerefMut for ComBoxData<T>
where
    T: ComClass,
{
    fn deref_mut(&mut self) -> &mut T
    {
        &mut self.value
    }
}

impl<T> std::ops::Deref for ComBox<T>
where
    T: ComClass,
{
    type Target = T;
    fn deref(&self) -> &T
    {
        unsafe { &(*self.data).value }
    }
}

impl<T> std::ops::DerefMut for ComBox<T>
where
    T: ComClass,
{
    fn deref_mut(&mut self) -> &mut T
    {
        unsafe { &mut (*self.data).value }
    }
}

impl<T: Default + ComClass> Default for ComBox<T>
{
    fn default() -> Self
    {
        ComBox::new(T::default())
    }
}