Skip to content

Commit

Permalink
dyn casting to implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Arlie Davis committed May 23, 2024
1 parent 40d35fa commit a38fc30
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 3 deletions.
9 changes: 9 additions & 0 deletions crates/libs/core/src/com_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ pub trait ComObjectInner: Sized {
/// This ensures that our requirement -- that safe Rust code never own a `<foo>_Impl` value
/// directly -- is met.
fn into_object(self) -> ComObject<Self>;

/// Dynamic cast
fn cast_from(unknown: &IUnknown) -> crate::Result<ComObject<Self>>
where
Self::Outer: core::any::Any + 'static,
{
let dynamic_cast = unknown.cast::<crate::IDynamicCast>()?;
dynamic_cast.downcast_object::<Self>()
}
}

/// Describes the COM interfaces implemented by a specific COM object.
Expand Down
144 changes: 144 additions & 0 deletions crates/libs/core/src/dynamic_cast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! Provides support for dynamic casts from a COM interface ([`IDynamicCast`]) to a Rust
//! implementation object.
//!
//! # Implementation notes
//!
//! This implementation manually implements the [`IDynamicCast`] type and its associated
//! [`IDynamicCast_Vtbl`] and [`IDynamicCast_Impl`] items, instead of using the `#[implement]`
//! macro. This is necessary for safely encapsulating unsafety at the interface boundary.
//!
//! It would be possible for `IDynamicCast_Impl` to define a method `fn as_any(&self) -> &dyn Any`.
//! However, such an implementation would only have access to the "inner" type. We want access to
//! the "outer" type. We solve this by defining the sole implementation of [`IDynamicCast`] within
//! this crate, and having its implementation directly cast from the COM interface pointer to the
//! _outer_ object pointer (the `MyApp_Impl` type). This allows us to increase the reference count
//! and to synthesize new [`ComObject`] instances.
//!
//! The manual implementation of [`IDynamicCast`] is also needed because the implementation defines
//! a method that uses the `Rust` calling convention, rather than `system`. This is necessary
//! because the method must use Rust types (`&dyn Any`).
use crate::imp::E_NOINTERFACE;
use crate::*;
use core::any::Any;
use core::ffi::c_void;

/// An interface that allows for dynamic casting, for all Rust types that implement [`core::any::Any`].
#[repr(transparent)]
#[derive(Clone)]
pub struct IDynamicCast(IUnknown);

unsafe impl Interface for IDynamicCast {
type Vtable = IDynamicCast_Vtbl;
const IID: GUID = GUID::from_u128(0xae49d5cb_143f_431c_874c_2729336e4eca);
}

#[doc(hidden)]
#[repr(C)]
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
pub struct IDynamicCast_Vtbl {
pub base__: IUnknown_Vtbl,

/// Returns a `*const dyn Any` for the _outer_ object of a COM object implementation.
///
/// In practical terms the purpose of implementations of this method is to return the pointer
/// to `&dyn Any` vtable, and to adjust the object pointer from `this` (which points to the
/// COM interface) to `&self` (which points to the MyApp_Impl outer type).
pub as_any: unsafe extern "Rust" fn(this: *mut c_void) -> *const dyn Any,
}

impl IDynamicCast_Vtbl {
pub const fn new<Identity: IUnknownImpl<Impl = Impl> + Any + 'static, Impl: IDynamicCast_Impl, const OFFSET: isize>() -> Self {
unsafe extern "Rust" fn as_any<Identity: IUnknownImpl<Impl = Impl> + Any + 'static, Impl: IDynamicCast_Impl, const OFFSET: isize>(this: *mut c_void) -> *const dyn Any {
let this_inner = (this as *mut *mut c_void).offset(OFFSET) as *mut Impl; // <-- this is the "inner" object pointer, e.g. MyApp
let this_outer = this_inner.byte_sub(Identity::INNER_BYTE_OFFSET) as *const Identity; // <-- this is the "outer" object pointer, e.g. MyApp_Impl
let outer_any: &dyn Any = &(*this_outer); // <-- this cast works because of the + Any + 'static constraints
outer_any as *const dyn Any
}

Self { base__: IUnknown_Vtbl::new::<Identity, OFFSET>(), as_any: as_any::<Identity, Impl, OFFSET> }
}

pub fn matches(iid: &GUID) -> bool {
iid == &<IDynamicCast as Interface>::IID
}
}

/// Allows for dynamic casting from COM interfaces to implementation types, for all implementation
/// types that implement `Any`.
///
/// This trait doesn't really do anything. It exists mainly so that the `#[implement]` macro does
/// not need any special knowledge of the `IDynamicCast` interface. It can still generate references
/// to `IDynamicCast_Impl`, even if they don't do anything.
#[allow(non_camel_case_types)]
pub trait IDynamicCast_Impl {}

/// The IDynamicCast_Impl trait doesn't actually do much.
impl<T: ComObjectInner> IDynamicCast_Impl for T where T::Outer: Any + 'static {}

impl IDynamicCast {
/// Casts this object to `&dyn Any`. The returned reference points to the _outer_ object, e.g.
/// the `MyApp_Impl` object, not the inner object.
///
/// This method is private in order to reduce confusion about outer vs. inner objects.
/// App code should use the [`Self::is`], [`Self::downcast_ref`], or [`Self::downcast_object`]
/// methods, since those use trait constraints to eliminate any confusion about outer vs. inner
/// objects.
fn as_any(&self) -> &dyn Any {
unsafe { &*(self.vtable().as_any)(self.as_raw()) }
}

/// Checks whether this object's implementation type is `T`.
///
/// `T` should be an "inner" object, e.g. `MyApp`.
pub fn is<T>(&self) -> bool
where
T: ComObjectInner,
T::Outer: IUnknownImpl<Impl = T> + Any + 'static,
{
self.as_any().is::<T::Outer>()
}

/// Casts this object to a given implementation type. `T` is the _inner_ object type, e.g. `MyApp`,
/// not the _outer_ type (`MyApp_Impl`). However, you get back a reference to the outer type.
/// (How cool is that?!)
pub fn downcast_ref<T>(&self) -> Result<&T::Outer>
where
T: ComObjectInner,
T::Outer: IUnknownImpl<Impl = T> + Any + 'static,
{
if let Some(it) = self.as_any().downcast_ref::<T::Outer>() {
Ok(it)
} else {
Err(E_NOINTERFACE.into())
}
}

/// Casts and acquires an owned reference.
///
/// This performs the cast and then calls AddRef on the underlying object.
pub fn downcast_object<T>(&self) -> Result<ComObject<T>>
where
T: ComObjectInner,
T::Outer: IUnknownImpl<Impl = T> + Any + 'static,
{
let outer = self.downcast_ref::<T>()?;
Ok(outer.to_object())
}
}

impl IUnknown {
/// Dynamic cast to a `ComObject`.
///
/// This function uses `QueryInterface` to get the `IDynamicCast` interface, then uses
/// `IDynamicCast` to cast to the implementation object.
pub fn cast_impl<T>(&self) -> Result<ComObject<T>>
where
T: ComObjectInner,
T::Outer: IUnknownImpl<Impl = T> + Any + 'static,
{
let dynamic_cast = self.cast::<IDynamicCast>()?;
dynamic_cast.downcast_object::<T>()
}
}
2 changes: 2 additions & 0 deletions crates/libs/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod agile_reference;
mod array;
mod as_impl;
mod com_object;
mod dynamic_cast;
#[cfg(feature = "std")]
mod event;
mod guid;
Expand Down Expand Up @@ -67,6 +68,7 @@ pub use weak::*;
pub use windows_implement::implement;
pub use windows_interface::interface;
pub use windows_result::*;
pub use dynamic_cast::*;

/// Attempts to load the factory object for the given WinRT class.
/// This can be used to access COM interfaces implemented on a Windows Runtime class factory.
Expand Down
15 changes: 15 additions & 0 deletions crates/libs/core/src/unknown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ pub trait IUnknownImpl {
{
<Self as ComObjectInterface<I>>::as_interface_ref(self).to_owned()
}

/// Creates a new owned reference to this object.
///
/// # Safety
///
/// This function can only be safely called by `<Foo>_Impl` objects that are embedded in a
/// `ComObject`. Since we only allow safe Rust code to access these objects using a `ComObject`
/// or a `&<Foo>_Impl` that points within a `ComObject`, this is safe.
fn to_object(&self) -> ComObject<Self::Impl>
where
Self::Impl: ComObjectInner<Outer = Self>;

/// The distance from the start of `<Foo>_Impl` to the `this` field within it. The `this` field
/// contains the `MyApp` instance.
const INNER_BYTE_OFFSET: usize;
}

impl IUnknown_Vtbl {
Expand Down
13 changes: 12 additions & 1 deletion crates/libs/implement/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro:
unsafe fn Release(self_: *mut Self) -> u32 {
let remaining = (*self_).count.release();
if remaining == 0 {
_ = ::windows_core::imp::Box::from_raw(self_ as *const Self as *mut Self);
_ = ::windows_core::imp::Box::from_raw(self_);
}
remaining
}
Expand All @@ -247,6 +247,17 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro:
&*((inner as *const Self::Impl as *const *const ::core::ffi::c_void)
.sub(#offset_of_this_in_pointers_token) as *const Self)
}

fn to_object(&self) -> ::windows_core::ComObject<Self::Impl> {
self.count.add_ref();
unsafe {
::windows_core::ComObject::from_raw(
::core::ptr::NonNull::new_unchecked(self as *const Self as *mut Self)
)
}
}

const INNER_BYTE_OFFSET: usize = #offset_of_this_in_pointers_token * ::core::mem::size_of::<*const ::core::ffi::c_void>();
}

impl #generics #original_ident::#generics where #constraints {
Expand Down
38 changes: 36 additions & 2 deletions crates/tests/implement_core/src/com_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use std::borrow::Borrow;
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::Arc;
use windows_core::{
implement, interface, ComObject, IUnknown, IUnknownImpl, IUnknown_Vtbl, InterfaceRef,
implement, interface, ComObject, IDynamicCast, IUnknown, IUnknownImpl, IUnknown_Vtbl,
Interface, InterfaceRef,
};

#[interface("818f2fd1-d479-4398-b286-a93c4c7904d1")]
Expand All @@ -19,7 +20,7 @@ unsafe trait IBar: IUnknown {
fn say_hello(&self);
}

#[implement(IFoo, IBar)]
#[implement(IFoo, IBar, IDynamicCast)]
struct MyApp {
x: u32,
tombstone: Arc<Tombstone>,
Expand Down Expand Up @@ -333,6 +334,39 @@ fn from_inner_ref() {
unsafe { ibar.say_hello() };
}

#[test]
fn to_object() {
let app = MyApp::new(42);
let tombstone = app.tombstone.clone();
let app_outer: &MyApp_Impl = &app;

let second_app = app_outer.to_object();
assert!(!tombstone.is_dead());

println!("x = {}", unsafe { second_app.get_x() });

drop(second_app);
assert!(!tombstone.is_dead());

drop(app);

assert!(tombstone.is_dead());
}

#[test]
fn dynamic_cast() {
let app = MyApp::new(42);
let unknown = app.to_interface::<IUnknown>();
let idyn = unknown.cast::<IDynamicCast>().unwrap();

assert!(!idyn.is::<SendableThing>());
assert!(idyn.is::<MyApp>());

let _dyn_app: &MyApp_Impl = idyn.downcast_ref::<MyApp>().unwrap();

let _dyn_owned: ComObject<MyApp> = unknown.cast_impl().unwrap();
}

// This tests that we can place a type that is not Send in a ComObject.
// Compilation is sufficient to test.
#[implement(IBar)]
Expand Down

0 comments on commit a38fc30

Please sign in to comment.