Skip to content

Commit 7cec74f

Browse files
authored
Implement static COM objects (#3144)
1 parent 12f4621 commit 7cec74f

File tree

7 files changed

+179
-9
lines changed

7 files changed

+179
-9
lines changed

crates/libs/core/src/com_object.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,68 @@ impl<T: ComObjectInner> Borrow<T> for ComObject<T> {
330330
self.get()
331331
}
332332
}
333+
334+
/// Enables applications to define COM objects using static storage. This is useful for factory
335+
/// objects, stateless objects, or objects which use need to contain or use mutable global state.
336+
///
337+
/// COM objects that are defined using `StaticComObject` have their storage placed directly in
338+
/// static storage; they are not stored in the heap.
339+
///
340+
/// COM objects defined using `StaticComObject` do have a reference count and this reference
341+
/// count is adjusted when owned COM interface references (e.g. `IFoo` and `IUnknown`) are created
342+
/// for the object. The reference count is initialized to 1.
343+
///
344+
/// # Example
345+
///
346+
/// ```rust,ignore
347+
/// #[implement(IFoo)]
348+
/// struct MyApp {
349+
/// // ...
350+
/// }
351+
///
352+
/// static MY_STATIC_APP: StaticComObject<MyApp> = MyApp { ... }.into_static();
353+
///
354+
/// fn get_my_static_ifoo() -> IFoo {
355+
/// MY_STATIC_APP.to_interface()
356+
/// }
357+
/// ```
358+
pub struct StaticComObject<T>
359+
where
360+
T: ComObjectInner,
361+
{
362+
outer: T::Outer,
363+
}
364+
365+
// IMPORTANT: Do not expose any methods that return mutable access to the contents of StaticComObject.
366+
// Doing so would violate our safety invariants. For example, we provide a Deref impl but it would
367+
// be unsound to provide a DerefMut impl.
368+
impl<T> StaticComObject<T>
369+
where
370+
T: ComObjectInner,
371+
{
372+
/// Wraps `outer` in a `StaticComObject`.
373+
pub const fn from_outer(outer: T::Outer) -> Self {
374+
Self { outer }
375+
}
376+
}
377+
378+
impl<T> StaticComObject<T>
379+
where
380+
T: ComObjectInner,
381+
{
382+
/// Gets access to the contained value.
383+
pub const fn get(&'static self) -> &'static T::Outer {
384+
&self.outer
385+
}
386+
}
387+
388+
impl<T> core::ops::Deref for StaticComObject<T>
389+
where
390+
T: ComObjectInner,
391+
{
392+
type Target = T::Outer;
393+
394+
fn deref(&self) -> &Self::Target {
395+
&self.outer
396+
}
397+
}

crates/libs/core/src/imp/ref_count.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub struct RefCount(pub(crate) AtomicI32);
66

77
impl RefCount {
88
/// Creates a new `RefCount` with an initial value of `1`.
9-
pub fn new(count: u32) -> Self {
9+
pub const fn new(count: u32) -> Self {
1010
Self(AtomicI32::new(count as i32))
1111
}
1212

crates/libs/core/src/imp/weak_ref_count.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use core::sync::atomic::{AtomicIsize, Ordering};
1010
pub struct WeakRefCount(AtomicIsize);
1111

1212
impl WeakRefCount {
13-
pub fn new() -> Self {
13+
pub const fn new() -> Self {
1414
Self(AtomicIsize::new(1))
1515
}
1616

crates/libs/core/src/weak.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub struct Weak<I: Interface>(Option<imp::IWeakReference>, PhantomData<I>);
77

88
impl<I: Interface> Weak<I> {
99
/// Creates a new `Weak` object without any backing object.
10-
pub fn new() -> Self {
10+
pub const fn new() -> Self {
1111
Self(None, PhantomData)
1212
}
1313

crates/libs/implement/src/lib.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,38 @@ pub fn implement(
179179
const IDENTITY: ::windows_core::IInspectable_Vtbl = ::windows_core::IInspectable_Vtbl::new::<Self, #identity_type, 0>();
180180
}
181181

182+
impl #generics #original_ident::#generics where #constraints {
183+
/// This converts a partially-constructed COM object (in the sense that it contains
184+
/// application state but does not yet have vtable and reference count constructed)
185+
/// into a `StaticComObject`. This allows the COM object to be stored in static
186+
/// (global) variables.
187+
pub const fn into_static(self) -> ::windows_core::StaticComObject<Self> {
188+
::windows_core::StaticComObject::from_outer(self.into_outer())
189+
}
190+
191+
// This constructs an "outer" object. This should only be used by the implementation
192+
// of the outer object, never by application code.
193+
//
194+
// The callers of this function (`into_static` and `into_object`) are both responsible
195+
// for maintaining one of our invariants: Application code never has an owned instance
196+
// of the outer (implementation) type. into_static() maintains this invariant by
197+
// returning a wrapped StaticComObject value, which owns its contents but never gives
198+
// application code a way to mutably access its contents. This prevents the refcount
199+
// shearing problem.
200+
//
201+
// TODO: Make it impossible for app code to call this function, by placing it in a
202+
// module and marking this as private to the module.
203+
#[inline(always)]
204+
const fn into_outer(self) -> #impl_ident::#generics {
205+
#impl_ident::#generics {
206+
identity: &#impl_ident::#generics::IDENTITY,
207+
vtables: (#(&#impl_ident::#generics::VTABLES.#offset,)*),
208+
this: self,
209+
count: ::windows_core::imp::WeakRefCount::new(),
210+
}
211+
}
212+
}
213+
182214
impl #generics ::windows_core::ComObjectInner for #original_ident::#generics where #constraints {
183215
type Outer = #impl_ident::#generics;
184216

@@ -191,12 +223,7 @@ pub fn implement(
191223
// This is why this function returns ComObject<Self> instead of returning #impl_ident.
192224

193225
fn into_object(self) -> ::windows_core::ComObject<Self> {
194-
let boxed = ::windows_core::imp::Box::new(#impl_ident::#generics {
195-
identity: &#impl_ident::#generics::IDENTITY,
196-
vtables: (#(&#impl_ident::#generics::VTABLES.#offset,)*),
197-
this: self,
198-
count: ::windows_core::imp::WeakRefCount::new(),
199-
});
226+
let boxed = ::windows_core::imp::Box::<#impl_ident::#generics>::new(self.into_outer());
200227
unsafe {
201228
let ptr = ::windows_core::imp::Box::into_raw(boxed);
202229
::windows_core::ComObject::from_raw(

crates/tests/implement_core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
mod com_chain;
77
mod com_object;
8+
mod static_com_object;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Unit tests for `windows_core::StaticComObject`
2+
3+
use std::sync::atomic::{AtomicU32, Ordering::SeqCst};
4+
use windows_core::{
5+
implement, interface, ComObject, IUnknown, IUnknownImpl, IUnknown_Vtbl, InterfaceRef,
6+
StaticComObject,
7+
};
8+
9+
#[interface("818f2fd1-d479-4398-b286-a93c4c7904d1")]
10+
unsafe trait INumberFactory: IUnknown {
11+
fn next(&self) -> u32;
12+
13+
fn add(&self, x: u32, y: u32) -> u32;
14+
}
15+
16+
#[implement(INumberFactory)]
17+
struct MyFactory {
18+
x: AtomicU32,
19+
}
20+
21+
impl INumberFactory_Impl for MyFactory_Impl {
22+
unsafe fn next(&self) -> u32 {
23+
self.x.fetch_add(1, SeqCst)
24+
}
25+
26+
unsafe fn add(&self, x: u32, y: u32) -> u32 {
27+
x + y
28+
}
29+
}
30+
31+
static NUMBER_FACTORY_INSTANCE: StaticComObject<MyFactory> = MyFactory {
32+
x: AtomicU32::new(100),
33+
}
34+
.into_static();
35+
36+
#[test]
37+
fn as_interface() {
38+
let factory_outer: &MyFactory_Impl = NUMBER_FACTORY_INSTANCE.get();
39+
let ifactory: InterfaceRef<INumberFactory> = factory_outer.as_interface::<INumberFactory>();
40+
41+
// Produce the next number. We don't verify the value since tests are multi-threaded.
42+
// This just demonstrates that you can have shared state with interior mutability (such as
43+
// atomics) in a static COM object.
44+
let n = unsafe { ifactory.next() };
45+
println!("n = {n:?}");
46+
47+
assert_eq!(unsafe { ifactory.add(333, 444) }, 777);
48+
}
49+
50+
// This tests that we can safely AddRef/Release a StaticComObject.
51+
#[test]
52+
fn to_interface() {
53+
let factory_outer: &MyFactory_Impl = NUMBER_FACTORY_INSTANCE.get();
54+
let ifactory: INumberFactory = factory_outer.to_interface::<INumberFactory>();
55+
assert_eq!(unsafe { ifactory.add(333, 444) }, 777);
56+
drop(ifactory);
57+
}
58+
59+
#[test]
60+
fn to_object() {
61+
let factory_outer: &MyFactory_Impl = NUMBER_FACTORY_INSTANCE.get();
62+
let factory_object: ComObject<MyFactory> = factory_outer.to_object();
63+
assert_eq!(unsafe { factory_object.add(333, 444) }, 777);
64+
}
65+
66+
// This tests the behavior when dropping a StaticComObject. Since static variables are never
67+
// dropped, this isn't relevant to normal usage. However, if app code constructs a StaticComObject
68+
// in local variables (not statics) and them drops them, then we still need well-defined behavior.
69+
// Basically, we are testing that the refererence-count field does not panic when being dropped
70+
// with a non-zero reference count.
71+
#[test]
72+
fn drop_half_constructed() {
73+
let _static_com_object: StaticComObject<MyFactory> = MyFactory {
74+
x: AtomicU32::new(0),
75+
}
76+
.into_static();
77+
}

0 commit comments

Comments
 (0)