diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 619164cb87..fc8d84900b 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -176,6 +176,8 @@ jobs: run: cargo clippy -p test_helpers - name: Clippy test_implement run: cargo clippy -p test_implement + - name: Clippy test_implement_core + run: cargo clippy -p test_implement_core - name: Clippy test_interface run: cargo clippy -p test_interface - name: Clippy test_interface_core diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b1d7685a7..39fb7543b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -202,6 +202,8 @@ jobs: run: cargo test -p test_helpers --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_implement run: cargo test -p test_implement --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Test test_implement_core + run: cargo test -p test_implement_core --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_interface run: cargo test -p test_interface --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_interface_core @@ -256,10 +258,10 @@ jobs: run: cargo test -p test_targets --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_unions run: cargo test -p test_unions --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_variant - run: cargo test -p test_variant --target ${{ matrix.target }} ${{ matrix.etc }} - name: Clean run: cargo clean + - name: Test test_variant + run: cargo test -p test_variant --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_wdk run: cargo test -p test_wdk --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_weak diff --git a/crates/libs/core/src/as_impl.rs b/crates/libs/core/src/as_impl.rs index 9bcae8ebc3..97f0e28ffc 100644 --- a/crates/libs/core/src/as_impl.rs +++ b/crates/libs/core/src/as_impl.rs @@ -6,5 +6,15 @@ pub trait AsImpl { /// /// The caller needs to ensure that `self` is actually implemented by the /// implementation `T`. - unsafe fn as_impl(&self) -> &T; + unsafe fn as_impl(&self) -> &T { + self.as_impl_ptr().as_ref() + } + + /// Returns a pointer to the implementation object. + /// + /// # Safety + /// + /// The caller needs to ensure that `self` is actually implemented by the + /// implementation `T`. + unsafe fn as_impl_ptr(&self) -> core::ptr::NonNull; } diff --git a/crates/libs/core/src/com_object.rs b/crates/libs/core/src/com_object.rs new file mode 100644 index 0000000000..c26d64c727 --- /dev/null +++ b/crates/libs/core/src/com_object.rs @@ -0,0 +1,275 @@ +use crate::{AsImpl, IUnknown, IUnknownImpl, Interface, InterfaceRef}; +use core::borrow::Borrow; +use core::ops::Deref; +use core::ptr::NonNull; + +/// Identifies types that can be placed in [`ComObject`]. +/// +/// This trait links types that can be placed in `ComObject` with the types generated by the +/// `#[implement]` macro. The `#[implement]` macro generates implementations of this trait. +/// The generated types contain the vtable layouts and refcount-related fields for the COM +/// object implementation. +/// +/// This trait is an implementation detail of the Windows crates. +/// User code should not deal directly with this trait. +/// +/// This trait is sort of the reverse of [`IUnknownImpl`]. This trait allows user code to use +/// `ComObject] instead of `ComObject`. +pub trait ComObjectInner { + /// The generated `_Impl` type (aka the "boxed" type or "outer" type). + type Outer: IUnknownImpl; +} + +/// Describes the COM interfaces implemented by a specific COM object. +/// +/// The `#[implement]` macro generates implementations of this trait. Implementations are attached +/// to the "outer" types generated by `#[implement]`, e.g. the `MyApp_Impl` type. Each +/// implementation knows how to locate the interface-specific field within `MyApp_Impl`. +/// +/// This trait is an implementation detail of the Windows crates. +/// User code should not deal directly with this trait. +pub trait ComObjectInterface { + /// Gets a borrowed interface that is implemented by `T`. + fn as_interface_ref(&self) -> InterfaceRef<'_, I>; +} + +/// A counted pointer to a type that implements COM interfaces, where the object has been +/// placed in the heap (boxed). +/// +/// This type exists so that you can place an object into the heap and query for COM interfaces, +/// without losing the safe reference to the implementation object. +/// +/// Because the pointer inside this type is known to be non-null, `Option>` should +/// always have the same size as a single pointer. +/// +/// # Safety +/// +/// The contained `ptr` field is an owned, reference-counted pointer to a _pinned_ `Pin>`. +/// Although this code does not currently use `Pin`, it takes care not to expose any unsafe semantics +/// to safe code. However, code that calls unsafe functions on [`ComObject`] must, like all unsafe code, +/// understand and preserve invariants. +#[repr(transparent)] +pub struct ComObject { + ptr: NonNull, +} + +impl ComObject { + /// Allocates a heap cell (box) and moves `value` into it. Returns a counted pointer to `value`. + pub fn new(value: T) -> Self { + unsafe { + let box_ = T::Outer::new_box(value); + Self { ptr: NonNull::new_unchecked(Box::into_raw(box_)) } + } + } + + /// Gets a reference to the shared object stored in the box. + /// + /// [`ComObject`] also implements [`Deref`], so you can often deref directly into the object. + /// For those situations where using the [`Deref`] impl is inconvenient, you can use + /// this method to explicitly get a reference to the contents. + #[inline(always)] + pub fn get(&self) -> &T { + self.get_box().get_impl() + } + + /// Gets a reference to the shared object's heap box. + #[inline(always)] + fn get_box(&self) -> &T::Outer { + unsafe { self.ptr.as_ref() } + } + + // Note that we _do not_ provide a way to get a mutable reference to the outer box. + // It's ok to return `&mut T`, but not `&mut T::Outer`. That would allow someone to replace the + // contents of the entire object (box and reference count), which could lead to UB. + // This could maybe be solved by returning `Pin<&mut T::Outer>`, but that requires some + // additional thinking. + + /// Gets a mutable reference to the object stored in the box, if the reference count + /// is exactly 1. If there are multiple references to this object then this returns `None`. + #[inline(always)] + pub fn get_mut(&mut self) -> Option<&mut T> { + if self.is_reference_count_one() { + // SAFETY: We must only return &mut T, *NOT* &mut T::Outer. + // Returning T::Outer would allow swapping the contents of the object, which would + // allow (incorrectly) modifying the reference count. + unsafe { Some(self.ptr.as_mut().get_impl_mut()) } + } else { + None + } + } + + /// If this object has only a single object reference (i.e. this [`ComObject`] is the only + /// reference to the heap allocation), then this method will extract the inner `T` + /// (and return it in an `Ok`) and then free the heap allocation. + /// + /// If there is more than one reference to this object, then this returns `Err(self)`. + #[inline(always)] + pub fn take(self) -> Result { + if self.is_reference_count_one() { + let outer_box: Box = unsafe { core::mem::transmute(self) }; + Ok(outer_box.into_inner()) + } else { + Err(self) + } + } + + /// Casts to a given interface type. + /// + /// This always performs a `QueryInterface`, even if `T` is known to implement `I`. + /// If you know that `T` implements `I`, then use [`Self::as_interface`] or [`Self::to_interface`] because + /// those functions do not require a dynamic `QueryInterface` call. + #[inline(always)] + pub fn cast(&self) -> windows_core::Result + where + T::Outer: ComObjectInterface, + { + let unknown = self.as_interface::(); + unknown.cast() + } + + /// Gets a borrowed reference to an interface that is implemented by `T`. + /// + /// The returned reference does not have an additional reference count. + /// You can AddRef it by calling [`Self::to_owned`]. + #[inline(always)] + pub fn as_interface(&self) -> InterfaceRef<'_, I> + where + T::Outer: ComObjectInterface, + { + self.get_box().as_interface_ref() + } + + /// Gets an owned (counted) reference to an interface that is implemented by this [`ComObject`]. + #[inline(always)] + pub fn to_interface(&self) -> I + where + T::Outer: ComObjectInterface, + { + self.as_interface::().to_owned() + } + + /// Converts `self` into an interface that it implements. + /// + /// This does not need to adjust reference counts because `self` is consumed. + #[inline(always)] + pub fn into_interface(self) -> I + where + T::Outer: ComObjectInterface, + { + unsafe { + let raw = self.get_box().as_interface_ref().as_raw(); + core::mem::forget(self); + I::from_raw(raw) + } + } +} + +impl Default for ComObject { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl Drop for ComObject { + fn drop(&mut self) { + unsafe { + T::Outer::Release(self.ptr.as_ptr()); + } + } +} + +impl Clone for ComObject { + #[inline(always)] + fn clone(&self) -> Self { + unsafe { + self.ptr.as_ref().AddRef(); + Self { ptr: self.ptr } + } + } +} + +impl AsRef for ComObject +where + IUnknown: From + AsImpl, +{ + #[inline(always)] + fn as_ref(&self) -> &T { + self.get() + } +} + +impl Deref for ComObject { + type Target = T::Outer; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.get_box() + } +} + +// There is no DerefMut implementation because we cannot statically guarantee +// that the reference count is 1, which is a requirement for getting exclusive +// access to the contents of the object. Use get_mut() for dynamically-checked +// exclusive access. + +impl From for ComObject { + fn from(value: T) -> ComObject { + ComObject::new(value) + } +} + +// Delegate hashing, if implemented. +impl core::hash::Hash for ComObject { + fn hash(&self, state: &mut H) { + self.get().hash(state); + } +} + +// If T is Send (or Sync) then the ComObject is also Send (or Sync). +// Since the actual object storage is in the heap, the object is never moved. +unsafe impl Send for ComObject {} +unsafe impl Sync for ComObject {} + +impl PartialEq for ComObject { + fn eq(&self, other: &ComObject) -> bool { + let inner_self: &T = self.get(); + let other_self: &T = other.get(); + inner_self == other_self + } +} + +impl Eq for ComObject {} + +impl PartialOrd for ComObject { + fn partial_cmp(&self, other: &Self) -> Option { + let inner_self: &T = self.get(); + let other_self: &T = other.get(); + ::partial_cmp(inner_self, other_self) + } +} + +impl Ord for ComObject { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + let inner_self: &T = self.get(); + let other_self: &T = other.get(); + ::cmp(inner_self, other_self) + } +} + +impl core::fmt::Debug for ComObject { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(self.get(), f) + } +} + +impl core::fmt::Display for ComObject { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(self.get(), f) + } +} + +impl Borrow for ComObject { + fn borrow(&self) -> &T { + self.get() + } +} diff --git a/crates/libs/core/src/imp/mod.rs b/crates/libs/core/src/imp/mod.rs index 84f27d8f87..7f7ca49d3e 100644 --- a/crates/libs/core/src/imp/mod.rs +++ b/crates/libs/core/src/imp/mod.rs @@ -98,3 +98,6 @@ macro_rules! define_interface { #[doc(hidden)] pub use define_interface; + +#[doc(hidden)] +pub use std::boxed::Box; diff --git a/crates/libs/core/src/imp/weak_ref_count.rs b/crates/libs/core/src/imp/weak_ref_count.rs index 92a4150735..266f329272 100644 --- a/crates/libs/core/src/imp/weak_ref_count.rs +++ b/crates/libs/core/src/imp/weak_ref_count.rs @@ -16,6 +16,11 @@ impl WeakRefCount { self.0.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count_or_pointer| bool::then_some(!is_weak_ref(count_or_pointer), count_or_pointer + 1)).map(|u| u as u32 + 1).unwrap_or_else(|pointer| unsafe { TearOff::decode(pointer).strong_count.add_ref() }) } + #[inline(always)] + pub fn is_one(&self) -> bool { + self.0.load(Ordering::Acquire) == 1 + } + pub fn release(&self) -> u32 { self.0.fetch_update(Ordering::Release, Ordering::Relaxed, |count_or_pointer| bool::then_some(!is_weak_ref(count_or_pointer), count_or_pointer - 1)).map(|u| u as u32 - 1).unwrap_or_else(|pointer| unsafe { let tear_off = TearOff::decode(pointer); diff --git a/crates/libs/core/src/interface.rs b/crates/libs/core/src/interface.rs index 74e1452640..b7acfa7ce7 100644 --- a/crates/libs/core/src/interface.rs +++ b/crates/libs/core/src/interface.rs @@ -1,4 +1,7 @@ use super::*; +use core::ffi::c_void; +use core::marker::PhantomData; +use core::ptr::NonNull; /// Provides low-level access to an interface vtable. /// @@ -18,6 +21,7 @@ pub unsafe trait Interface: Sized + Clone { /// A reference to the interface's vtable #[doc(hidden)] + #[inline(always)] fn vtable(&self) -> &Self::Vtable { // SAFETY: the implementor of the trait guarantees that `Self` is castable to its vtable unsafe { self.assume_vtable::() } @@ -30,19 +34,21 @@ pub unsafe trait Interface: Sized + Clone { /// This is safe if `T` is an equivalent interface to `Self` or a super interface. /// In other words, `T::Vtable` must be equivalent to the beginning of `Self::Vtable`. #[doc(hidden)] + #[inline(always)] unsafe fn assume_vtable(&self) -> &T::Vtable { &**(self.as_raw() as *mut *mut T::Vtable) } /// Returns the raw COM interface pointer. The resulting pointer continues to be owned by the `Interface` implementation. #[inline(always)] - fn as_raw(&self) -> *mut std::ffi::c_void { + fn as_raw(&self) -> *mut c_void { // SAFETY: implementors of this trait must guarantee that the implementing type has a pointer in-memory representation unsafe { std::mem::transmute_copy(self) } } /// Returns the raw COM interface pointer and releases ownership. It the caller's responsibility to release the COM interface pointer. - fn into_raw(self) -> *mut std::ffi::c_void { + #[inline(always)] + fn into_raw(self) -> *mut c_void { // SAFETY: implementors of this trait must guarantee that the implementing type has a pointer in-memory representation let raw = self.as_raw(); std::mem::forget(self); @@ -55,7 +61,7 @@ pub unsafe trait Interface: Sized + Clone { /// /// The `raw` pointer must be owned by the caller and represent a valid COM interface pointer. In other words, /// it must point to a vtable beginning with the `IUnknown` function pointers and match the vtable of `Interface`. - unsafe fn from_raw(raw: *mut std::ffi::c_void) -> Self { + unsafe fn from_raw(raw: *mut c_void) -> Self { std::mem::transmute_copy(&raw) } @@ -65,7 +71,8 @@ pub unsafe trait Interface: Sized + Clone { /// /// The `raw` pointer must be a valid COM interface pointer. In other words, it must point to a vtable /// beginning with the `IUnknown` function pointers and match the vtable of `Interface`. - unsafe fn from_raw_borrowed(raw: &*mut std::ffi::c_void) -> Option<&Self> { + #[inline(always)] + unsafe fn from_raw_borrowed(raw: &*mut c_void) -> Option<&Self> { if raw.is_null() { None } else { @@ -77,15 +84,29 @@ pub unsafe trait Interface: Sized + Clone { /// /// The name `cast` is preferred to `query` because there is a WinRT method named query but not one /// named cast. + #[inline(always)] fn cast(&self) -> Result { - let mut result = None; - // SAFETY: `result` is valid for writing an interface pointer and it is safe // to cast the `result` pointer as `T` on success because we are using the `IID` tied // to `T` which the implementor of `Interface` has guaranteed is correct - unsafe { _ = self.query(&T::IID, &mut result as *mut _ as _) }; + unsafe { + // If query() returns a failure code then we propagate that failure code to the caller. + // In that case, we ignore the contents of 'result' (which will _not_ be dropped, + // because MaybeUninit intentionally does not drop its contents). + // + // 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::>::zeroed(); + self.query(&T::IID, result.as_mut_ptr() as _).ok()?; - result.ok_or_else(|| Error::from_hresult(imp::E_NOINTERFACE)) + // If we get here, then query() has succeeded, but we still need to double-check + // that the output pointer is non-null. + if let Some(obj) = result.assume_init() { + Ok(obj) + } else { + Err(imp::E_POINTER.into()) + } + } } /// Attempts to create a [`Weak`] reference to this object. @@ -98,17 +119,93 @@ pub unsafe trait Interface: Sized + Clone { /// # Safety /// /// `interface` must be a non-null, valid pointer for writing an interface pointer. - unsafe fn query(&self, iid: *const GUID, interface: *mut *mut std::ffi::c_void) -> HRESULT { + #[inline(always)] + unsafe fn query(&self, iid: *const GUID, interface: *mut *mut c_void) -> HRESULT { if Self::UNKNOWN { (self.assume_vtable::().QueryInterface)(self.as_raw(), iid, interface) } else { panic!("Non-COM interfaces cannot be queried.") } } + + /// Creates an `InterfaceRef` for this reference. The `InterfaceRef` tracks lifetimes statically, + /// and eliminates the need for dynamic reference count adjustments (AddRef/Release). + fn to_ref(&self) -> InterfaceRef<'_, Self> { + InterfaceRef::from_interface(self) + } } /// # Safety #[doc(hidden)] -pub unsafe fn from_raw_borrowed(raw: &*mut std::ffi::c_void) -> Option<&T> { +pub unsafe fn from_raw_borrowed(raw: &*mut c_void) -> Option<&T> { T::from_raw_borrowed(raw) } + +/// This has the same memory representation as `IFoo`, but represents a borrowed interface pointer. +/// +/// This type has no `Drop` impl; it does not AddRef/Release the given interface. However, because +/// it has a lifetime parameter, it always represents a non-null pointer to an interface. +#[repr(transparent)] +pub struct InterfaceRef<'a, I>(NonNull, PhantomData<&'a I>); + +impl<'a, I> Copy for InterfaceRef<'a, I> {} + +impl<'a, I> Clone for InterfaceRef<'a, I> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, I: core::fmt::Debug + Interface> core::fmt::Debug for InterfaceRef<'a, I> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(&**self, f) + } +} + +impl<'a, I: Interface> InterfaceRef<'a, I> { + /// Creates an `InterfaceRef` from a raw pointer. _This is extremely dangerous, since there + /// is no lifetime tracking at all!_ + /// + /// # Safety + /// The caller must guarantee that the `'a` lifetime parameter is bound by context to a correct + /// lifetime. + #[inline(always)] + pub unsafe fn from_raw(ptr: NonNull) -> Self { + Self(ptr, PhantomData) + } + + /// Creates an `InterfaceRef` from an interface reference. This safely associates the lifetime + /// of the interface reference with the `'a` parameter of `InterfaceRef`. This allows for + /// lifetime checking _without_ calling AddRef/Release on the underlying lifetime, which can + /// improve efficiency. + #[inline(always)] + pub fn from_interface(interface: &I) -> Self { + unsafe { + // SAFETY: new_unchecked() should be valid because Interface::as_raw should always + // return a non-null pointer. + Self(NonNull::new_unchecked(interface.as_raw()), PhantomData) + } + } + + /// Calls AddRef on the underlying COM interface and returns an "owned" (counted) reference. + #[inline(always)] + pub fn to_owned(self) -> I { + (*self).clone() + } +} + +impl<'a, 'i: 'a, I: Interface> From<&'i I> for InterfaceRef<'a, I> { + #[inline(always)] + fn from(interface: &'a I) -> InterfaceRef<'a, I> { + InterfaceRef::from_interface(interface) + } +} + +impl<'a, I: Interface> core::ops::Deref for InterfaceRef<'a, I> { + type Target = I; + + #[inline(always)] + fn deref(&self) -> &I { + unsafe { core::mem::transmute(self) } + } +} diff --git a/crates/libs/core/src/lib.rs b/crates/libs/core/src/lib.rs index 997ab2afd5..17ff1c81e5 100644 --- a/crates/libs/core/src/lib.rs +++ b/crates/libs/core/src/lib.rs @@ -14,6 +14,7 @@ pub mod imp; mod agile_reference; mod array; mod as_impl; +mod com_object; mod event; mod guid; mod handles; @@ -36,6 +37,7 @@ mod weak; pub use agile_reference::*; pub use array::*; pub use as_impl::*; +pub use com_object::*; pub use event::*; pub use guid::*; pub use handles::*; diff --git a/crates/libs/core/src/ref.rs b/crates/libs/core/src/ref.rs index b1025fb985..b8d82a8cca 100644 --- a/crates/libs/core/src/ref.rs +++ b/crates/libs/core/src/ref.rs @@ -1,8 +1,9 @@ use super::*; +use core::marker::PhantomData; /// A borrowed type with the same memory layout as the type itself that can be used to construct ABI-compatible function signatures. #[repr(transparent)] -pub struct Ref<'a, T: Type>(T::Abi, std::marker::PhantomData<&'a T>); +pub struct Ref<'a, T: Type>(T::Abi, PhantomData<&'a T>); impl<'a, T: Type, Abi = *mut std::ffi::c_void>> Ref<'a, T> { /// Converts the argument to a [Result<&T>] reference. diff --git a/crates/libs/core/src/unknown.rs b/crates/libs/core/src/unknown.rs index c14a40dda1..8817b57f8d 100644 --- a/crates/libs/core/src/unknown.rs +++ b/crates/libs/core/src/unknown.rs @@ -1,18 +1,21 @@ use super::*; +use core::ffi::c_void; +use core::ptr::NonNull; /// All COM interfaces (and thus WinRT classes and interfaces) implement /// [IUnknown](https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown) /// under the hood to provide reference-counted lifetime management as well as the ability /// to query for additional interfaces that the object may implement. #[repr(transparent)] -pub struct IUnknown(std::ptr::NonNull); +pub struct IUnknown(NonNull); #[doc(hidden)] #[repr(C)] +#[allow(non_camel_case_types)] pub struct IUnknown_Vtbl { - pub QueryInterface: unsafe extern "system" fn(this: *mut std::ffi::c_void, iid: *const GUID, interface: *mut *mut std::ffi::c_void) -> HRESULT, - pub AddRef: unsafe extern "system" fn(this: *mut std::ffi::c_void) -> u32, - pub Release: unsafe extern "system" fn(this: *mut std::ffi::c_void) -> u32, + pub QueryInterface: unsafe extern "system" fn(this: *mut c_void, iid: *const GUID, interface: *mut *mut c_void) -> HRESULT, + pub AddRef: unsafe extern "system" fn(this: *mut c_void) -> u32, + pub Release: unsafe extern "system" fn(this: *mut c_void) -> u32, } unsafe impl Interface for IUnknown { @@ -56,19 +59,34 @@ impl std::fmt::Debug for IUnknown { } } +/// The `#[implement]` macro generates implementations of this trait for the types +/// that it generates, e.g. `MyApp_Impl`, +/// +/// `ComObject` uses this trait to interact with boxed COM objects. #[doc(hidden)] pub trait IUnknownImpl { + /// The contained user type, e.g. `MyApp`. Also known as the "inner" type. type Impl; + + /// Initializes a new heap box and returns it. + fn new_box(value: Self::Impl) -> Box; + /// Get a reference to the backing implementation. fn get_impl(&self) -> &Self::Impl; + /// Get a mutable reference to the contained (inner) object. + fn get_impl_mut(&mut self) -> &mut Self::Impl; + + /// Consumes the box and returns the contained (inner) object. This is the opposite of `new_box`. + fn into_inner(self) -> Self::Impl; + /// The classic `QueryInterface` method from COM. /// /// # Safety /// /// This function is safe to call as long as the interface pointer is non-null and valid for writes /// of an interface pointer. - unsafe fn QueryInterface(&self, iid: *const GUID, interface: *mut *mut std::ffi::c_void) -> HRESULT; + unsafe fn QueryInterface(&self, iid: *const GUID, interface: *mut *mut c_void) -> HRESULT; /// Increments the reference count of the interface fn AddRef(&self) -> u32; @@ -77,27 +95,64 @@ pub trait IUnknownImpl { /// /// # Safety /// - /// This function should only be called when the interfacer pointer is no longer used as calling `Release` + /// This function should only be called when the interface pointer is no longer used as calling `Release` /// on a non-aliased interface pointer and then using that interface pointer may result in use after free. - unsafe fn Release(&self) -> u32; + /// + /// This function takes `*mut Self` because the object may be freed by the time this method returns. + /// Taking `&self` would violate Rust's rules on reference lifetime. + unsafe fn Release(self_: *mut Self) -> u32; + + /// Returns `true` if the reference count of the box is equal to 1. + fn is_reference_count_one(&self) -> bool; /// Gets the trust level of the current object. unsafe fn GetTrustLevel(&self, value: *mut i32) -> HRESULT; + + /// Given a reference to an inner type, returns a reference to the outer shared type. + /// + /// # Safety + /// + /// This function should only be called from methods that implement COM interfaces, i.e. + /// implementations of methods on `IFoo_Impl` traits. + // TODO: This can be made safe, if IFoo_Impl are moved to the Object_Impl types. + // That requires some substantial redesign, though. + unsafe fn from_inner_ref(inner: &Self::Impl) -> &Self; + + /// Gets a borrowed reference to an interface that is implemented by this ComObject. + /// + /// The returned reference does not have an additional reference count. + /// You can AddRef it by calling to_owned(). + #[inline(always)] + fn as_interface(&self) -> InterfaceRef<'_, I> + where + Self: ComObjectInterface, + { + >::as_interface_ref(self) + } + + /// Gets an owned (counted) reference to an interface that is implemented by this ComObject. + #[inline(always)] + fn to_interface(&self) -> I + where + Self: ComObjectInterface, + { + >::as_interface_ref(self).to_owned() + } } impl IUnknown_Vtbl { pub const fn new() -> Self { - unsafe extern "system" fn QueryInterface(this: *mut std::ffi::c_void, iid: *const GUID, interface: *mut *mut std::ffi::c_void) -> HRESULT { - let this = (this as *mut *mut std::ffi::c_void).offset(OFFSET) as *mut T; + unsafe extern "system" fn QueryInterface(this: *mut c_void, iid: *const GUID, interface: *mut *mut c_void) -> HRESULT { + let this = (this as *mut *mut c_void).offset(OFFSET) as *mut T; (*this).QueryInterface(iid, interface) } - unsafe extern "system" fn AddRef(this: *mut std::ffi::c_void) -> u32 { - let this = (this as *mut *mut std::ffi::c_void).offset(OFFSET) as *mut T; + unsafe extern "system" fn AddRef(this: *mut c_void) -> u32 { + let this = (this as *mut *mut c_void).offset(OFFSET) as *mut T; (*this).AddRef() } - unsafe extern "system" fn Release(this: *mut std::ffi::c_void) -> u32 { - let this = (this as *mut *mut std::ffi::c_void).offset(OFFSET) as *mut T; - (*this).Release() + unsafe extern "system" fn Release(this: *mut c_void) -> u32 { + let this = (this as *mut *mut c_void).offset(OFFSET) as *mut T; + T::Release(this) } Self { QueryInterface: QueryInterface::, AddRef: AddRef::, Release: Release:: } } diff --git a/crates/libs/implement/src/lib.rs b/crates/libs/implement/src/lib.rs index 15ad9cace5..3f6ec0c102 100644 --- a/crates/libs/implement/src/lib.rs +++ b/crates/libs/implement/src/lib.rs @@ -45,6 +45,7 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: let original_type2 = original_type.clone(); let original_type2 = syn::parse_macro_input!(original_type2 as syn::ItemStruct); + let vis = &original_type2.vis; let original_ident = original_type2.ident; let mut constraints = quote! {}; @@ -82,6 +83,10 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: } }); + // The distance from the beginning of the generated type to the 'this' field, in units of pointers (not bytes). + let offset_of_this_in_pointers = 1 + attributes.implement.len(); + let offset_of_this_in_pointers_token = proc_macro2::Literal::usize_unsuffixed(offset_of_this_in_pointers); + let trust_level = proc_macro2::Literal::usize_unsuffixed(attributes.trust_level); let conversions = attributes.implement.iter().enumerate().map(|(enumerate, implement)| { @@ -89,23 +94,36 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: let offset = proc_macro2::Literal::usize_unsuffixed(enumerate); quote! { impl #generics ::core::convert::From<#original_ident::#generics> for #interface_ident where #constraints { + #[inline(always)] fn from(this: #original_ident::#generics) -> Self { let this = #impl_ident::#generics::new(this); - let mut this = ::core::mem::ManuallyDrop::new(::std::boxed::Box::new(this)); + let mut this = ::core::mem::ManuallyDrop::new(::windows_core::imp::Box::new(this)); let vtable_ptr = &this.vtables.#offset; // SAFETY: interfaces are in-memory equivalent to pointers to their vtables. unsafe { ::core::mem::transmute(vtable_ptr) } } } + + impl #generics ::windows_core::ComObjectInterface<#interface_ident> for #impl_ident::#generics where #constraints { + #[inline(always)] + fn as_interface_ref(&self) -> ::windows_core::InterfaceRef<'_, #interface_ident> { + unsafe { + let interface_ptr = &self.vtables.#offset; + ::core::mem::transmute(interface_ptr) + } + } + } + impl #generics ::windows_core::AsImpl<#original_ident::#generics> for #interface_ident where #constraints { // SAFETY: the offset is guranteed to be in bounds, and the implementation struct // is guaranteed to live at least as long as `self`. - unsafe fn as_impl(&self) -> &#original_ident::#generics { + #[inline(always)] + unsafe fn as_impl_ptr(&self) -> ::core::ptr::NonNull<#original_ident::#generics> { let this = ::windows_core::Interface::as_raw(self); // Subtract away the vtable offset plus 1, for the `identity` field, to get // to the impl struct which contains that original implementation type. let this = (this as *mut *mut ::core::ffi::c_void).sub(1 + #offset) as *mut #impl_ident::#generics; - &(*this).this + ::core::ptr::NonNull::new_unchecked(::core::ptr::addr_of!((*this).this) as *const #original_ident::#generics as *mut #original_ident::#generics) } } } @@ -113,10 +131,10 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: let tokens = quote! { #[repr(C)] - struct #impl_ident #generics where #constraints { + #vis struct #impl_ident #generics where #constraints { identity: *const ::windows_core::IInspectable_Vtbl, vtables: (#(*const #vtbl_idents,)*), - this: #original_ident::#generics, + this: #original_ident::#generics, count: ::windows_core::imp::WeakRefCount, } impl #generics #impl_ident::#generics where #constraints { @@ -131,19 +149,46 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: } } } - impl #generics ::windows_core::IUnknownImpl for #impl_ident::#generics where #constraints { + + impl #generics ::windows_core::ComObjectInner for #original_ident::#generics where #constraints { + type Outer = #impl_ident::#generics; + } + + impl #generics ::windows_core::IUnknownImpl for #impl_ident::#generics where #constraints { type Impl = #original_ident::#generics; + + fn new_box(value: Self::Impl) -> ::windows_core::imp::Box { + ::windows_core::imp::Box::new(Self::new(value)) + } + + #[inline(always)] fn get_impl(&self) -> &Self::Impl { &self.this } + + #[inline(always)] + fn get_impl_mut(&mut self) -> &mut Self::Impl { + &mut self.this + } + + #[inline(always)] + fn is_reference_count_one(&self) -> bool { + self.count.is_one() + } + + #[inline(always)] + fn into_inner(self) -> Self::Impl { + self.this + } + unsafe fn QueryInterface(&self, iid: *const ::windows_core::GUID, interface: *mut *mut ::core::ffi::c_void) -> ::windows_core::HRESULT { if iid.is_null() || interface.is_null() { - return ::windows_core::HRESULT(-2147467261); // E_POINTER + return ::windows_core::imp::E_POINTER; } let iid = &*iid; - *interface = if iid == &<::windows_core::IUnknown as ::windows_core::Interface>::IID + let interface_ptr: *mut ::core::ffi::c_void = if iid == &<::windows_core::IUnknown as ::windows_core::Interface>::IID || iid == &<::windows_core::IInspectable as ::windows_core::Interface>::IID || iid == &<::windows_core::imp::IAgileObject as ::windows_core::Interface>::IID { &self.identity as *const _ as *mut _ @@ -151,37 +196,50 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: ::core::ptr::null_mut() }; - if !(*interface).is_null() { + if !interface_ptr.is_null() { + *interface = interface_ptr; self.count.add_ref(); return ::windows_core::HRESULT(0); } - *interface = self.count.query(iid, &self.identity as *const _ as *mut _); + let interface_ptr = self.count.query(iid, &self.identity as *const _ as *mut _); + *interface = interface_ptr; - if (*interface).is_null() { - ::windows_core::HRESULT(-2147467262) // E_NOINTERFACE + if interface_ptr.is_null() { + ::windows_core::imp::E_NOINTERFACE } else { ::windows_core::HRESULT(0) } } + + #[inline(always)] fn AddRef(&self) -> u32 { self.count.add_ref() } - unsafe fn Release(&self) -> u32 { - let remaining = self.count.release(); + + #[inline(always)] + unsafe fn Release(self_: *mut Self) -> u32 { + let remaining = (*self_).count.release(); if remaining == 0 { - _ = ::std::boxed::Box::from_raw(self as *const Self as *mut Self); + _ = ::windows_core::imp::Box::from_raw(self_ as *const Self as *mut Self); } remaining } + unsafe fn GetTrustLevel(&self, value: *mut i32) -> ::windows_core::HRESULT { if value.is_null() { - return ::windows_core::HRESULT(-2147467261); // E_POINTER + return ::windows_core::imp::E_POINTER; } *value = #trust_level; ::windows_core::HRESULT(0) } - } + + unsafe fn from_inner_ref(inner: &Self::Impl) -> &Self { + &*((inner as *const Self::Impl as *const *const ::core::ffi::c_void) + .sub(#offset_of_this_in_pointers_token) as *const Self) + } + } + impl #generics #original_ident::#generics where #constraints { /// Try casting as the provided interface /// @@ -189,31 +247,71 @@ pub fn implement(attributes: proc_macro::TokenStream, original_type: proc_macro: /// /// This function can only be safely called if `self` has been heap allocated and pinned using /// the mechanisms provided by `implement` macro. + #[inline(always)] unsafe fn cast(&self) -> ::windows_core::Result { let boxed = (self as *const _ as *const *mut ::core::ffi::c_void).sub(1 + #interfaces_len) as *mut #impl_ident::#generics; - let mut result = ::std::ptr::null_mut(); + let mut result = ::core::ptr::null_mut(); _ = <#impl_ident::#generics as ::windows_core::IUnknownImpl>::QueryInterface(&*boxed, &I::IID, &mut result); ::windows_core::Type::from_abi(result) } } + impl #generics ::core::convert::From<#original_ident::#generics> for ::windows_core::IUnknown where #constraints { + #[inline(always)] fn from(this: #original_ident::#generics) -> Self { let this = #impl_ident::#generics::new(this); - let boxed = ::core::mem::ManuallyDrop::new(::std::boxed::Box::new(this)); + let boxed = ::core::mem::ManuallyDrop::new(::windows_core::imp::Box::new(this)); unsafe { ::core::mem::transmute(&boxed.identity) } } } + impl #generics ::core::convert::From<#original_ident::#generics> for ::windows_core::IInspectable where #constraints { + #[inline(always)] fn from(this: #original_ident::#generics) -> Self { let this = #impl_ident::#generics::new(this); - let boxed = ::core::mem::ManuallyDrop::new(::std::boxed::Box::new(this)); + let boxed = ::core::mem::ManuallyDrop::new(::windows_core::imp::Box::new(this)); unsafe { ::core::mem::transmute(&boxed.identity) } } } + + impl #generics ::windows_core::ComObjectInterface<::windows_core::IUnknown> for #impl_ident::#generics where #constraints { + #[inline(always)] + fn as_interface_ref(&self) -> ::windows_core::InterfaceRef<'_, ::windows_core::IUnknown> { + unsafe { + let interface_ptr = &self.identity; + ::core::mem::transmute(interface_ptr) + } + } + } + + impl #generics ::windows_core::AsImpl<#original_ident::#generics> for ::windows_core::IUnknown where #constraints { + // SAFETY: the offset is guranteed to be in bounds, and the implementation struct + // is guaranteed to live at least as long as `self`. + #[inline(always)] + unsafe fn as_impl_ptr(&self) -> ::core::ptr::NonNull<#original_ident::#generics> { + let this = ::windows_core::Interface::as_raw(self); + // Subtract away the vtable offset plus 1, for the `identity` field, to get + // to the impl struct which contains that original implementation type. + let this = (this as *mut *mut ::core::ffi::c_void).sub(1) as *mut #impl_ident::#generics; + ::core::ptr::NonNull::new_unchecked(::core::ptr::addr_of!((*this).this) as *const #original_ident::#generics as *mut #original_ident::#generics) + } + } + + impl #generics ::core::ops::Deref for #impl_ident::#generics where #constraints { + type Target = #original_ident::#generics; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.this + } + } + + // We intentionally do not provide a DerefMut impl, due to paranoia around soundness. + #(#conversions)* }; diff --git a/crates/libs/interface/src/lib.rs b/crates/libs/interface/src/lib.rs index 16cefaa277..b2f38b310a 100644 --- a/crates/libs/interface/src/lib.rs +++ b/crates/libs/interface/src/lib.rs @@ -67,7 +67,7 @@ macro_rules! expected_token { /// ```rust,ignore /// #[windows_interface::interface("8CEEB155-2849-4ce5-9448-91FF70E1E4D9")] /// unsafe trait IUIAnimationVariable: IUnknown { -/// //^ parses this +/// //^ parses this /// fn GetValue(&self, value: *mut f64) -> HRESULT; /// } /// ``` @@ -103,10 +103,10 @@ impl Interface { const IID: ::windows_core::GUID = #guid; } impl ::windows_core::RuntimeName for #name {} - impl ::std::ops::Deref for #name { + impl ::core::ops::Deref for #name { type Target = #parent; fn deref(&self) -> &Self::Target { - unsafe { ::std::mem::transmute(self) } + unsafe { ::core::mem::transmute(self) } } } #com_trait @@ -132,12 +132,14 @@ impl Interface { if m.is_result() { quote! { + #[inline(always)] #vis unsafe fn #name<#(#generics),*>(&self, #(#params),*) #ret { (::windows_core::Interface::vtable(self).#name)(::windows_core::Interface::as_raw(self), #(#args),*).ok() } } } else { quote! { + #[inline(always)] #vis unsafe fn #name<#(#generics),*>(&self, #(#params),*) #ret { (::windows_core::Interface::vtable(self).#name)(::windows_core::Interface::as_raw(self), #(#args),*) } @@ -300,15 +302,15 @@ impl Interface { Self { #(#entries),* } } } - struct #implvtbl_name (::std::marker::PhantomData); + struct #implvtbl_name (::core::marker::PhantomData); impl #implvtbl_name { const VTABLE: #vtable_name = #vtable_name::new::(); } impl #name { fn new<'a, T: #trait_name>(this: &'a T) -> ::windows_core::ScopedInterface<'a, #name> { let this = ::windows_core::ScopedHeap { vtable: &#implvtbl_name::::VTABLE as *const _ as *const _, this: this as *const _ as *const _ }; - let this = ::std::mem::ManuallyDrop::new(::std::boxed::Box::new(this)); - unsafe { ::windows_core::ScopedInterface::new(::std::mem::transmute(&this.vtable)) } + let this = ::core::mem::ManuallyDrop::new(::windows_core::imp::Box::new(this)); + unsafe { ::windows_core::ScopedInterface::new(::core::mem::transmute(&this.vtable)) } } } } @@ -353,7 +355,7 @@ impl Interface { if let Some(parent) = &self.parent { quote!(#parent) } else { - quote!(::std::ptr::NonNull<::std::ffi::c_void>) + quote!(::core::ptr::NonNull<::core::ffi::c_void>) } } @@ -428,7 +430,7 @@ impl syn::parse::Parse for Interface { /// /// ```rust,ignore /// #[windows_interface::interface("8CEEB155-2849-4ce5-9448-91FF70E1E4D9")] -/// //^ parses this +/// //^ parses this /// unsafe trait IUIAnimationVariable: IUnknown { /// fn GetValue(&self, value: *mut f64) -> HRESULT; /// } @@ -510,7 +512,7 @@ impl syn::parse::Parse for Guid { /// #[windows_interface::interface("8CEEB155-2849-4ce5-9448-91FF70E1E4D9")] /// unsafe trait IUIAnimationVariable: IUnknown { /// fn GetValue(&self, value: *mut f64) -> HRESULT; -/// //^ parses this +/// //^ parses this /// } /// ``` struct InterfaceMethod { diff --git a/crates/tests/implement/Cargo.toml b/crates/tests/implement/Cargo.toml index e183d0f302..fe027b5e8f 100644 --- a/crates/tests/implement/Cargo.toml +++ b/crates/tests/implement/Cargo.toml @@ -25,3 +25,6 @@ features = [ [dependencies.windows-core] path = "../../libs/core" + +[dependencies] +static_assertions = "1.1" diff --git a/crates/tests/implement/tests/no_std.rs b/crates/tests/implement/tests/no_std.rs new file mode 100644 index 0000000000..2dbb71ff3e --- /dev/null +++ b/crates/tests/implement/tests/no_std.rs @@ -0,0 +1,23 @@ +// Compilation is sufficient to test. +// This verifies that it is possible to use #[interface] +// and #[implement] in a crate that uses #![no_std]. + +#![no_std] + +use windows::core::{implement, interface, IUnknown, IUnknown_Vtbl}; + +#[interface("36bb4e8d-0385-477e-a090-e70675f37781")] +pub unsafe trait IFoo: IUnknown { + fn x(&self) -> u32; +} + +#[implement(IFoo)] +struct MyApp { + x: u32, +} + +impl IFoo_Impl for MyApp { + unsafe fn x(&self) -> u32 { + self.x + } +} diff --git a/crates/tests/implement_core/Cargo.toml b/crates/tests/implement_core/Cargo.toml new file mode 100644 index 0000000000..ac270d2f4d --- /dev/null +++ b/crates/tests/implement_core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test_implement_core" +version = "0.1.0" +edition = "2021" + +[dependencies.windows-core] +path = "../../libs/core" + +[dependencies] +static_assertions = "1.1" diff --git a/crates/tests/implement_core/src/com_object.rs b/crates/tests/implement_core/src/com_object.rs new file mode 100644 index 0000000000..a5d0eb2be9 --- /dev/null +++ b/crates/tests/implement_core/src/com_object.rs @@ -0,0 +1,364 @@ +//! Unit tests for `windows_core::ComObject` + +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, +}; + +#[interface("818f2fd1-d479-4398-b286-a93c4c7904d1")] +unsafe trait IFoo: IUnknown { + fn get_x(&self) -> u32; + + fn get_self_as_bar(&self) -> IBar; +} + +#[interface("687eb4b2-6df6-41a3-86c7-4b04b94ad2d8")] +unsafe trait IBar: IUnknown { + fn say_hello(&self); +} + +#[implement(IFoo, IBar)] +struct MyApp { + x: u32, + tombstone: Arc, +} + +impl IFoo_Impl for MyApp { + unsafe fn get_x(&self) -> u32 { + self.x + } + + unsafe fn get_self_as_bar(&self) -> IBar { + let outer = MyApp_Impl::from_inner_ref(self); + outer.to_interface() + } +} + +impl IBar_Impl for MyApp { + unsafe fn say_hello(&self) { + println!("Hello!"); + } +} + +impl Borrow for MyApp { + fn borrow(&self) -> &u32 { + &self.x + } +} + +impl core::fmt::Debug for MyApp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "x = {}", self.x) + } +} + +impl core::fmt::Display for MyApp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "x = {}", self.x) + } +} + +impl Default for MyApp { + fn default() -> Self { + Self { + x: 0, + tombstone: Arc::new(Tombstone::default()), + } + } +} + +impl std::hash::Hash for MyApp { + fn hash(&self, state: &mut H) { + self.x.hash(state); + } +} + +impl PartialEq for MyApp { + fn eq(&self, other: &u32) -> bool { + self.x == *other + } +} + +impl PartialEq for MyApp { + fn eq(&self, other: &MyApp) -> bool { + self.x == other.x + } +} + +impl Eq for MyApp {} + +/// This lets us detect when an object has been dropped. +#[derive(Default)] +struct Tombstone { + cell: AtomicBool, +} + +impl Tombstone { + fn is_dead(&self) -> bool { + self.cell.load(SeqCst) + } + + fn mark_dead(&self) { + self.cell.store(true, SeqCst); + } +} + +impl MyApp { + fn new(x: u32) -> ComObject { + ComObject::new(Self { + x, + tombstone: Arc::new(Tombstone::default()), + }) + } + + fn get_x_direct(&self) -> u32 { + self.x + } + + fn set_x(&mut self, x: u32) { + self.x = x; + } +} + +impl Drop for MyApp { + fn drop(&mut self) { + println!("MyApp::drop"); + self.tombstone.mark_dead(); + } +} + +#[test] +fn basic() { + let app: ComObject = MyApp::new(42); + let iunknown: IUnknown = app.cast().unwrap(); + let ifoo: IFoo = app.cast().unwrap(); + assert_eq!(unsafe { ifoo.get_x() }, 42); + + // check lifetimes + let tombstone = app.tombstone.clone(); + assert!(!tombstone.is_dead()); + + drop(app); + assert!(!tombstone.is_dead()); + + drop(iunknown); + assert!(!tombstone.is_dead()); + + drop(ifoo); + assert!(tombstone.is_dead()); +} + +#[test] +fn casting() { + let app: ComObject = MyApp::new(42); + let tombstone = app.tombstone.clone(); + + let ifoo: IFoo = app.cast().unwrap(); + assert_eq!(unsafe { app.get_x() }, 42); + + // check lifetimes + assert!(!tombstone.is_dead()); + + drop(app); + assert!(!tombstone.is_dead()); + + drop(ifoo); + assert!(tombstone.is_dead()); +} + +#[test] +fn clone() { + let app: ComObject = MyApp::new(42); + let ifoo: IFoo = app.cast().unwrap(); + let ifoo2 = ifoo.clone(); + + drop(ifoo); + drop(app); + assert_eq!(unsafe { ifoo2.get_x() }, 42); +} + +#[test] +fn get_mut() { + let mut app: ComObject = MyApp::new(42); + assert_eq!(app.get_x_direct(), 42); + + // refcount = 1 + app.get_mut().unwrap().set_x(50); + + assert_eq!(app.get_x_direct(), 50); + + let app2 = app.clone(); + // refcount = 2 + assert!(app.get_mut().is_none()); + + drop(app2); + + // refcount = 1 again + app.get_mut().unwrap().set_x(60); +} + +#[test] +fn take() { + let app: ComObject = MyApp::new(42); + let tombstone = app.tombstone.clone(); + // refcount = 1 + + let app2 = app.clone(); + // refcount = 2 + + let app2_rejected: ComObject = match app2.take() { + Ok(_unexpected) => panic!("take() should have failed"), + Err(e) => e, + }; + // refcount = 1 + + drop(app2_rejected); + // refcount = 1 + + match app.take() { + Ok(unwrapped_app) => { + // box destroyed + assert!(!tombstone.is_dead()); + assert_eq!(unwrapped_app.x, 42); + drop(unwrapped_app); + assert!(tombstone.is_dead()); + } + + Err(_unexpected) => { + panic!("take() should have succeeded"); + } + } +} + +#[test] +fn as_interface() { + let app = MyApp::new(42); + let tombstone = app.tombstone.clone(); + + // All ComObject implement IUnknown. + let _ = app.as_interface::(); + + let ifoo = app.as_interface::(); + assert_eq!(unsafe { ifoo.get_x() }, 42); + assert!(!tombstone.is_dead()); + + drop(app); + assert!(tombstone.is_dead()); +} + +#[test] +fn to_interface() { + let app = MyApp::new(42); + let tombstone = app.tombstone.clone(); + + // All ComObject implement IUnknown. + drop(app.to_interface::()); + + let ifoo = app.to_interface::(); + assert_eq!(unsafe { ifoo.get_x() }, 42); + assert!(!tombstone.is_dead()); + + drop(app); + assert!(!tombstone.is_dead()); + + drop(ifoo); + assert!(tombstone.is_dead()); +} + +#[test] +fn into_interface() { + let app = MyApp::new(42); + let tombstone = app.tombstone.clone(); + + let ifoo = app.into_interface::(); + assert_eq!(unsafe { ifoo.get_x() }, 42); + assert!(!tombstone.is_dead()); + + drop(ifoo); + assert!(tombstone.is_dead()); +} + +#[test] +fn construct_with_com_object_new() { + // Test that we can construct using ComObject::new(). + let app: ComObject = ComObject::new(MyApp::default()); + let _ = app; +} + +#[test] +fn construct_with_com_object_from() { + // Test that we can construct using ComObject::from(). + let app: ComObject = ComObject::from(MyApp::default()); + let _ = app; +} + +#[test] +fn construct_with_into() { + // Test that we can construct using ComObject::from(). + fn consume(_: ComObject) {} + + consume(MyApp::default().into()) +} + +#[test] +fn debug() { + let app = MyApp::new(100); + let s = format!("{:?}", app); + assert_eq!(s, "x = 100"); +} + +#[test] +fn display() { + let app = MyApp::new(200); + let s = format!("{}", app); + assert_eq!(s, "x = 200"); +} + +#[test] +fn hashable() { + use std::collections::HashMap; + + let mut map: HashMap, &'static str> = HashMap::new(); + map.insert(MyApp::new(100), "hello"); + map.insert(MyApp::new(200), "world"); +} + +#[test] +fn from_inner_ref() { + let app = MyApp::new(42); + let ifoo: InterfaceRef = app.as_interface(); + let ibar: IBar = unsafe { ifoo.get_self_as_bar() }; + unsafe { ibar.say_hello() }; +} + +// This tests that we can place a type that is not Send in a ComObject. +// Compilation is sufficient to test. +#[implement(IBar)] +struct UnsendableThing { + cell: core::cell::Cell, +} + +impl IBar_Impl for UnsendableThing { + unsafe fn say_hello(&self) { + println!("{}", self.cell.get()); + } +} + +static_assertions::assert_not_impl_all!(UnsendableThing: Send, Sync); +static_assertions::assert_not_impl_all!(ComObject: Send, Sync); + +#[implement(IBar)] +struct SendableThing { + arc: std::sync::Arc, +} + +impl IBar_Impl for SendableThing { + unsafe fn say_hello(&self) { + println!("{}", *self.arc); + } +} + +static_assertions::assert_impl_all!(SendableThing: Send, Sync); +static_assertions::assert_impl_all!(ComObject: Send, Sync); diff --git a/crates/tests/implement_core/src/lib.rs b/crates/tests/implement_core/src/lib.rs new file mode 100644 index 0000000000..e083fca649 --- /dev/null +++ b/crates/tests/implement_core/src/lib.rs @@ -0,0 +1,6 @@ +//! This contains tests that can be performed without compiling the `windows` +//! crate. This allows for faster test iteration. + +#![cfg(test)] + +mod com_object; diff --git a/crates/tests/interface/tests/no_std.rs b/crates/tests/interface/tests/no_std.rs new file mode 100644 index 0000000000..c5894809e0 --- /dev/null +++ b/crates/tests/interface/tests/no_std.rs @@ -0,0 +1,12 @@ +// Compilation is sufficient to test. +// This verifies that it is possible to use #[interface] +// in a crate that uses #![no_std]. + +#![no_std] + +use windows::core::{interface, IUnknown, IUnknown_Vtbl}; + +#[interface("36bb4e8d-0385-477e-a090-e70675f37781")] +pub unsafe trait IFoo: IUnknown { + fn x(&self) -> u32; +}