-
Notifications
You must be signed in to change notification settings - Fork 503
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Arlie Davis
committed
May 23, 2024
1 parent
40d35fa
commit a38fc30
Showing
6 changed files
with
218 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters