Skip to content

Commit

Permalink
remove IDynamicCast, make dyn support unconditional (well, almost)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arlie Davis committed May 24, 2024
1 parent 6cfa34f commit b87a217
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 167 deletions.
34 changes: 25 additions & 9 deletions crates/libs/core/src/com_object.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::imp::Box;
use crate::{AsImpl, IUnknown, IUnknownImpl, Interface, InterfaceRef};
use core::any::Any;
use core::borrow::Borrow;
use core::ops::Deref;
use core::ptr::NonNull;
Expand Down Expand Up @@ -39,15 +40,6 @@ 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 Expand Up @@ -205,6 +197,30 @@ impl<T: ComObjectInner> ComObject<T> {
I::from_raw(raw)
}
}

/// This casts the given COM interface to [`&dyn Any`]. It returns a reference to the "outer"
/// object, e.g. `MyApp_Impl`, not the inner `MyApp` object.
///
/// `T` must be a type that has been annotated with `#[implement]`; this is checked at
/// compile-time by the generic constraints of this method. However, note that the
/// returned `&dyn Any` refers to the _outer_ implementation object that was generated by
/// `#[implement]`, i.e. the `MyApp_Impl` type, not the inner `MyApp` type.
///
/// If the given object is not a Rust object, or is a Rust object but not `T`, then this
/// function will return `Err(E_NOINTERFACE)`.
///
/// The returned value is an owned (counted) reference; this function calls `AddRef` on the
/// underlying COM object. If you do not need an owned reference, then you can use the
/// [`Interface::cast_object_ref`] method instead, and avoid the cost of `AddRef` / `Release`.
pub fn cast_from<IR, I>(self, interface: IR) -> crate::Result<Self>
where
IR: AsRef<I>,
I: Interface,
T::Outer: Any + 'static + IUnknownImpl<Impl = T>,
{
let interface_ref: &I = interface.as_ref();
interface_ref.cast_object()
}
}

impl<T: ComObjectInner + Default> Default for ComObject<T> {
Expand Down
143 changes: 0 additions & 143 deletions crates/libs/core/src/dynamic_cast.rs

This file was deleted.

128 changes: 126 additions & 2 deletions crates/libs/core/src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::*;
use core::any::Any;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::mem::{forget, transmute_copy};
use core::mem::{forget, transmute_copy, MaybeUninit};
use core::ptr::NonNull;

/// Provides low-level access to an interface vtable.
Expand Down Expand Up @@ -97,7 +98,7 @@ pub unsafe trait Interface: Sized + Clone {
//
// This guards against implementations of COM interfaces which may store non-null values
// in 'result' but still return E_NOINTERFACE.
let mut result = core::mem::MaybeUninit::<Option<T>>::zeroed();
let mut result = MaybeUninit::<Option<T>>::zeroed();
self.query(&T::IID, result.as_mut_ptr() as _).ok()?;

// If we get here, then query() has succeeded, but we still need to double-check
Expand All @@ -110,6 +111,123 @@ pub unsafe trait Interface: Sized + Clone {
}
}

/// This casts the given COM interface to [`&dyn Any`].
///
/// Applications should generally _not_ call this method directly. Instead, use the
/// [`Interface::cast_object_ref`] or [`Interface::cast_object`] methods.
///
/// `T` must be a type that has been annotated with `#[implement]`; this is checked at
/// compile-time by the generic constraints of this method. However, note that the
/// returned `&dyn Any` refers to the _outer_ implementation object that was generated by
/// `#[implement]`, i.e. the `MyApp_Impl` type, not the inner `MyApp` type.
///
/// If the given object is not a Rust object, or is a Rust object but not `T`, then this
/// function will return `Err(E_NOINTERFACE)`.
///
/// # Safety
///
/// **IMPORTANT!!** This uses a non-standard protocol for QueryInterface! The `DYNAMIC_CAST_IID`
/// IID identifies this protocol, but there is no `IDynamicCast` interface. Instead, objects
/// that recognize `DYNAMIC_CAST_IID` simply store their `&dyn Any` directly at the interface
/// pointer that was passed to `QueryInterface. This means that the returned value has a
/// size that is twice as large (`size_of::<&dyn Any>() == 2 * size_of::<*const c_void>()`).
///
/// This means that callers that use this protocol cannot simply pass `&mut ptr` for
/// an ordinary single-pointer-sized pointer. Only this method understands this protocol.
///
/// Another part of this protocol is that the implementation of `QueryInterface` _does not_
/// AddRef the object. The caller must guarantee the liveness of the COM object. In Rust,
/// this means tying the lifetime of the IUnknown* that we used for the QueryInterface
/// call to the lifetime of the returned `&dyn Any` value.
///
/// This method preserves type safety and relies on these invariants:
///
/// * All `QueryInterface` implementations that recognize `DYNAMIC_CAST_IID` are generated by
/// the `#[implement]` macro and respect the rules described here.
#[inline(always)]
fn cast_to_any<T>(&self) -> Result<&dyn Any>
where
T: ComObjectInner,
T::Outer: Any + 'static + IUnknownImpl<Impl = T>,
{
unsafe {
let mut any_ref_arg: MaybeUninit<&dyn Any> = MaybeUninit::zeroed();
self.query(&DYNAMIC_CAST_IID, any_ref_arg.as_mut_ptr() as *mut *mut c_void).ok()?;
Ok(any_ref_arg.assume_init())
}
}

/// Returns `true` if the given COM interface refers to an implementation of `T`.
///
/// `T` must be a type that has been annotated with `#[implement]`; this is checked at
/// compile-time by the generic constraints of this method.
///
/// If the given object is not a Rust object, or is a Rust object but not `T`, then this
/// function will return `false`.
#[inline(always)]
fn is_object<T>(&self) -> bool
where
T: ComObjectInner,
T::Outer: Any + 'static + IUnknownImpl<Impl = T>,
{
if let Ok(any) = self.cast_to_any::<T>() {
any.is::<T::Outer>()
} else {
false
}
}

/// This casts the given COM interface to [`&dyn Any`]. It returns a reference to the "outer"
/// object, e.g. `&MyApp_Impl`, not the inner `&MyApp` object.
///
/// `T` must be a type that has been annotated with `#[implement]`; this is checked at
/// compile-time by the generic constraints of this method. However, note that the
/// returned `&dyn Any` refers to the _outer_ implementation object that was generated by
/// `#[implement]`, i.e. the `MyApp_Impl` type, not the inner `MyApp` type.
///
/// If the given object is not a Rust object, or is a Rust object but not `T`, then this
/// function will return `Err(E_NOINTERFACE)`.
///
/// The returned value is borrowed. If you need an owned (counted) reference, then use
/// [`Interface::cast_object`].
#[inline(always)]
fn cast_object_ref<T>(&self) -> Result<&T::Outer>
where
T: ComObjectInner,
T::Outer: Any + 'static + IUnknownImpl<Impl = T>,
{
let any: &dyn Any = self.cast_to_any::<T>()?;
if let Some(outer) = any.downcast_ref::<T::Outer>() {
Ok(outer)
} else {
Err(imp::E_NOINTERFACE.into())
}
}

/// This casts the given COM interface to [`&dyn Any`]. It returns a reference to the "outer"
/// object, e.g. `MyApp_Impl`, not the inner `MyApp` object.
///
/// `T` must be a type that has been annotated with `#[implement]`; this is checked at
/// compile-time by the generic constraints of this method. However, note that the
/// returned `&dyn Any` refers to the _outer_ implementation object that was generated by
/// `#[implement]`, i.e. the `MyApp_Impl` type, not the inner `MyApp` type.
///
/// If the given object is not a Rust object, or is a Rust object but not `T`, then this
/// function will return `Err(E_NOINTERFACE)`.
///
/// The returned value is an owned (counted) reference; this function calls `AddRef` on the
/// underlying COM object. If you do not need an owned reference, then you can use the
/// [`Interface::cast_object_ref`] method instead, and avoid the cost of `AddRef` / `Release`.
#[inline(always)]
fn cast_object<T>(&self) -> Result<ComObject<T>>
where
T: ComObjectInner,
T::Outer: Any + 'static + IUnknownImpl<Impl = T>,
{
let object_ref = self.cast_object_ref::<T>()?;
Ok(object_ref.to_object())
}

/// Attempts to create a [`Weak`] reference to this object.
fn downgrade(&self) -> Result<Weak<Self>> {
self.cast::<imp::IWeakReferenceSource>().and_then(|source| Weak::downgrade(&source))
Expand Down Expand Up @@ -210,3 +328,9 @@ impl<'a, I: Interface> core::ops::Deref for InterfaceRef<'a, I> {
unsafe { core::mem::transmute(self) }
}
}

/// This IID identifies a special protocol, used by [`Interface::cast_to_any`]. This is _not_
/// an ordinary COM interface; it uses special lifetime rules and a larger interface pointer.
/// See the comments on [`Interface::cast_to_any`].
#[doc(hidden)]
pub const DYNAMIC_CAST_IID: GUID = GUID::from_u128(0xae49d5cb_143f_431c_874c_2729336e4eca);
2 changes: 0 additions & 2 deletions crates/libs/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ mod agile_reference;
mod array;
mod as_impl;
mod com_object;
mod dynamic_cast;
#[cfg(feature = "std")]
mod event;
mod guid;
Expand All @@ -46,7 +45,6 @@ pub use agile_reference::*;
pub use array::*;
pub use as_impl::*;
pub use com_object::*;
pub use dynamic_cast::*;
#[cfg(feature = "std")]
pub use event::*;
pub use guid::*;
Expand Down
Loading

0 comments on commit b87a217

Please sign in to comment.