Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement COM interfaces on ComObject<T> instead of T #3071

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions crates/libs/bindgen/src/rust/implements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,15 @@ pub fn writer(writer: &Writer, def: metadata::TypeDef) -> TokenStream {

if has_unknown_base {
quote! {
unsafe extern "system" fn #name<#constraints Identity: windows_core::IUnknownImpl<Impl = Impl>, Impl: #impl_ident<#generic_names>, const OFFSET: isize> #vtbl_signature {
unsafe extern "system" fn #name<
#constraints
Identity: windows_core::IUnknownImpl,
OuterToImpl: ::windows_core::ComGetImpl<Identity>,
const OFFSET: isize
> #vtbl_signature where OuterToImpl::Impl: #impl_ident<#generic_names> {
// offset the `this` pointer by `OFFSET` times the size of a pointer and cast it as an IUnknown implementation
let this = (this as *const *const ()).offset(OFFSET) as *const Identity;
let this = (*this).get_impl();
let this_outer: &Identity = &*((this as *const *const ()).offset(OFFSET) as *const Identity);
let this = OuterToImpl::get_impl(this_outer);
#invoke_upcall
}
}
Expand All @@ -123,7 +128,7 @@ pub fn writer(writer: &Writer, def: metadata::TypeDef) -> TokenStream {
Some(metadata::Type::TypeDef(def, generics)) => {
let name = writer.type_def_name_imp(*def, generics, "_Vtbl");
if has_unknown_base {
methods.combine(&quote! { base__: #name::new::<Identity, Impl, OFFSET>(), });
methods.combine(&quote! { base__: #name::new::<Identity, OuterToImpl, OFFSET>(), });
} else {
methods.combine(&quote! { base__: #name::new::<Impl>(), });
}
Expand All @@ -136,7 +141,8 @@ pub fn writer(writer: &Writer, def: metadata::TypeDef) -> TokenStream {
for method in def.methods() {
let name = method_names.add(method);
if has_unknown_base {
methods.combine(&quote! { #name: #name::<#generic_names Identity, Impl, OFFSET>, });
methods
.combine(&quote! { #name: #name::<#generic_names Identity, OuterToImpl, OFFSET>, });
} else {
methods.combine(&quote! { #name: #name::<Impl>, });
}
Expand All @@ -151,7 +157,12 @@ pub fn writer(writer: &Writer, def: metadata::TypeDef) -> TokenStream {
#runtime_name
#features
impl<#constraints> #vtbl_ident<#generic_names> {
pub const fn new<Identity: windows_core::IUnknownImpl<Impl = Impl>, Impl: #impl_ident<#generic_names>, const OFFSET: isize>() -> #vtbl_ident<#generic_names> {
pub const fn new<
Identity: windows_core::IUnknownImpl,
OuterToImpl: ::windows_core::ComGetImpl<Identity>,
const OFFSET: isize
>() -> #vtbl_ident<#generic_names>
where OuterToImpl::Impl : #impl_ident<#generic_names> {
#(#method_impls)*
Self{
#methods
Expand Down
51 changes: 51 additions & 0 deletions crates/libs/core/src/com_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,54 @@ impl<T: ComObjectInner> Borrow<T> for ComObject<T> {
self.get()
}
}

/// Allows a COM object implementation to implement COM interfaces either on the "outer" type or
/// the "inner" type.
///
/// This trait is part of the implementation of `windows-rs` and is not meant to be used directly
/// by user code. This trait is not stable and may change at any time.
#[doc(hidden)]
pub trait ComGetImpl<T: ComObjectInner> {
/// The type that implements the COM interface.
type Impl;

/// At runtime, casts from the outer object type to the implementation type.
fn get_impl(object: &ComObject<T>) -> &Self::Impl;
}

/// Selects the "inner" type of a COM object implementation. This implementation uses the
/// `IUnknownImpl` trait both to specify the type that implements the COM interface and to
/// cast from `&Outer` to `&Inner` (i.e. from `&MyApp_Impl` to `&MyApp`).
///
/// This struct is part of the implementation of `windows-rs` and is not meant to be used directly
/// by user code. This trait is not stable and may change at any time.
#[doc(hidden)]
pub struct ComGetImplInner<T> {
_marker: core::marker::PhantomData<T>,
}

impl<T: ComObjectInner> ComGetImpl<T> for ComGetImplInner<T> {
type Impl = T;

fn get_impl(object: &ComObject<T>) -> &Self::Impl {
object.get_impl()
}
}

/// Selects the "outer" type of a COM object implementation. This is basically an identify function,
/// over types.
///
/// This struct is part of the implementation of `windows-rs` and is not meant to be used directly
/// by user code. This trait is not stable and may change at any time.
#[doc(hidden)]
pub struct ComGetImplOuter<T> {
_marker: core::marker::PhantomData<T>,
}

impl<T: ComObjectInner> ComGetImpl<T> for ComGetImplOuter<T> {
type Impl = ComObject<T>;

fn get_impl(object: &ComObject<T>) -> &Self::Impl {
object
}
}
2 changes: 1 addition & 1 deletion crates/libs/core/src/unknown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl core::fmt::Debug for IUnknown {
#[doc(hidden)]
pub trait IUnknownImpl {
/// The contained user type, e.g. `MyApp`. Also known as the "inner" type.
type Impl;
type Impl: ComObjectInner<Outer = Self>;

/// Get a reference to the backing implementation.
fn get_impl(&self) -> &Self::Impl;
Expand Down
61 changes: 54 additions & 7 deletions crates/libs/implement/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,16 @@ pub fn implement(
.enumerate()
.map(|(enumerate, implement)| {
let vtbl_ident = implement.to_vtbl_ident();
let outer_to_impl = match implement.impl_location {
ImplLocation::Outer => {
quote!(::windows_core::ComGetImplOuter<#original_ident #generics>)
}
ImplLocation::Inner => {
quote!(::windows_core::ComGetImplInner<#original_ident #generics>)
}
};
let offset = proc_macro2::Literal::isize_unsuffixed(-1 - enumerate as isize);
quote! { #vtbl_ident::new::<Self, #original_ident::#generics, #offset>() }
quote! { #vtbl_ident::new::<Self, #outer_to_impl, #offset>() }
});

let offset = attributes
Expand Down Expand Up @@ -389,6 +397,18 @@ pub fn implement(
struct ImplementType {
type_name: String,
generics: Vec<ImplementType>,
impl_location: ImplLocation,
}

/// Specifies whether a COM object implements COM interfaces on its "inner" or "outer" object.
///
/// The default, for backward compatibility, is inner. In the long-term, arguably all COM objects
/// should switch to defining interfaces on the outer object.
#[derive(Copy, Clone, Eq, PartialEq, Default)]
enum ImplLocation {
#[default]
Inner,
Outer,
}

impl ImplementType {
Expand Down Expand Up @@ -450,8 +470,9 @@ impl ImplementAttributes {
namespace.push_str(&input.ident.to_string());
self.walk_implement(&input.tree, namespace)?;
}
UseTree2::Name(_) => {
self.implement.push(tree.to_element_type(namespace)?);
UseTree2::Name(name) => {
self.implement
.push(tree.to_element_type(name.impl_location, namespace)?);
}
UseTree2::Group(input) => {
for tree in &input.items {
Expand All @@ -473,15 +494,19 @@ enum UseTree2 {
}

impl UseTree2 {
fn to_element_type(&self, namespace: &mut String) -> syn::parse::Result<ImplementType> {
fn to_element_type(
&self,
impl_location: ImplLocation,
namespace: &mut String,
) -> syn::parse::Result<ImplementType> {
match self {
UseTree2::Path(input) => {
if !namespace.is_empty() {
namespace.push_str("::");
}

namespace.push_str(&input.ident.to_string());
input.tree.to_element_type(namespace)
input.tree.to_element_type(impl_location, namespace)
}
UseTree2::Name(input) => {
let mut type_name = input.ident.to_string();
Expand All @@ -493,12 +518,13 @@ impl UseTree2 {
let mut generics = vec![];

for g in &input.generics {
generics.push(g.to_element_type(&mut String::new())?);
generics.push(g.to_element_type(impl_location, &mut String::new())?);
}

Ok(ImplementType {
type_name,
generics,
impl_location,
})
}
UseTree2::Group(input) => Err(syn::parse::Error::new(
Expand All @@ -518,6 +544,7 @@ struct UsePath2 {
struct UseName2 {
pub ident: syn::Ident,
pub generics: Vec<UseTree2>,
pub impl_location: ImplLocation,
}

struct UseGroup2 {
Expand Down Expand Up @@ -572,7 +599,27 @@ impl syn::parse::Parse for UseTree2 {
Vec::new()
};

Ok(UseTree2::Name(UseName2 { ident, generics }))
// Check for a suffix of `@Outer`, which specifies that an interface chain is
// implemented on the outer (MyApp_Impl) type, not the inner type.
let mut impl_location = ImplLocation::Inner;
if input.peek(syn::Token![@]) {
input.parse::<syn::Token![@]>()?;
let ident = input.parse::<syn::Ident>()?;
if ident == "Outer" {
impl_location = ImplLocation::Outer;
} else {
return Err(syn::Error::new(
ident.span(),
"the only supported suffix is @ Outer",
));
}
}

Ok(UseTree2::Name(UseName2 {
ident,
generics,
impl_location,
}))
}
} else if lookahead.peek(syn::token::Brace) {
let content;
Expand Down
85 changes: 60 additions & 25 deletions crates/libs/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ impl Interface {

if m.is_result() {
quote! {
#[inline(always)]
#vis unsafe fn #name<#(#generics),*>(&self, #(#params),*) #ret {
#[inline(always)]
#vis unsafe fn #name<#(#generics),*>(&self, #(#params),*) #ret {
(::windows_core::Interface::vtable(self).#name)(::windows_core::Interface::as_raw(self), #(#args),*).ok()
}
}
Expand Down Expand Up @@ -214,7 +214,7 @@ impl Interface {
let parent_vtable_generics = if self.parent_is_iunknown() {
quote!(Identity, OFFSET)
} else {
quote!(Identity, Impl, OFFSET)
quote!(Identity, OuterToImpl, OFFSET)
};
let parent_vtable = self.parent_vtable();

Expand Down Expand Up @@ -253,13 +253,40 @@ impl Interface {

if parent_vtable.is_some() {
quote! {
unsafe extern "system" fn #name<Identity: ::windows_core::IUnknownImpl<Impl = Impl>, Impl: #trait_name, const OFFSET: isize>(this: *mut ::core::ffi::c_void, #(#args),*) #ret {
let this = (this as *const *const ()).offset(OFFSET) as *const Identity;
let this_impl: &Impl = (*this).get_impl();
unsafe extern "system" fn #name<
Identity: ::windows_core::IUnknownImpl,
OuterToImpl: ::windows_core::ComGetImpl<Identity::Impl>,
const OFFSET: isize
>(
this: *mut ::core::ffi::c_void, // <-- This is the COM "this" pointer, which is not the same as &T or &T_Impl.
#(#args),*
) #ret
where
OuterToImpl::Impl : #trait_name
{
// This step is essentially a virtual dispatch adjustor thunk. Its purpose is to adjust
// the "this" pointer from the address used by the COM interface to the root of the
// MyApp_Impl object. Since a given MyApp_Impl may implement more than one COM interface
// (and more than one COM interface chain), we need to know how to get from COM's "this"
// back to &MyApp_Impl. The OFFSET constant gives us the value (in pointer-sized units).
let this_outer: &Identity = &*((this as *const *const ()).offset(OFFSET) as *const Identity);

let this_com_object: ::core::mem::ManuallyDrop<::windows_core::ComObject<Identity::Impl>>
= core::mem::transmute(this_outer);

let this_com_object_ref: &::windows_core::ComObject<Identity::Impl> = &this_com_object;

// This step selects the part of the MyApp_Impl object which implements a given COM interface,
// i.e. IFoo_Impl trait. There are really only two possibilities: either MyApp_Impl or MyApp
// implements a given IFoo_Impl trait. The ComGetImplInner and ComGetImplOuter types
// allow the code that specialized this function to select which one is used.
let this_impl: &OuterToImpl::Impl = OuterToImpl::get_impl(this_com_object_ref);

// Last, we invoke the implementation function.
// We use explicit <Impl as IFoo_Impl> so that we can select the correct method
// for situations where IFoo3 derives from IFoo2 and both declare a method with
// the same name.
<Impl as #trait_name>::#name(this_impl, #(#params),*).into()
<OuterToImpl::Impl as #trait_name>::#name(this_impl, #(#params),*).into()
}
}
} else {
Expand All @@ -274,24 +301,16 @@ impl Interface {
})
.collect::<Vec<_>>();

let entries = self
.methods
.iter()
.map(|m| {
let name = &m.name;
if parent_vtable.is_some() {
quote! {
#name: #name::<Identity, Impl, OFFSET>
}
} else {
quote! {
#name: #name::<Impl>
}
}
})
.collect::<Vec<_>>();

if let Some(parent_vtable) = parent_vtable {
let entries = self
.methods
.iter()
.map(|m| {
let name = &m.name;
quote!(#name: #name::<Identity, OuterToImpl, OFFSET>)
})
.collect::<Vec<_>>();

quote! {
#[repr(C)]
#[doc(hidden)]
Expand All @@ -300,7 +319,14 @@ impl Interface {
#(#vtable_entries)*
}
impl #vtable_name {
pub const fn new<Identity: ::windows_core::IUnknownImpl<Impl = Impl>, Impl: #trait_name, const OFFSET: isize>() -> Self {
pub const fn new<
Identity: ::windows_core::IUnknownImpl,
OuterToImpl: ::windows_core::ComGetImpl<Identity::Impl>,
const OFFSET: isize,
>() -> Self
where
OuterToImpl::Impl : #trait_name
{
#(#functions)*
Self { base__: #parent_vtable::new::<#parent_vtable_generics>(), #(#entries),* }
}
Expand All @@ -313,6 +339,15 @@ impl Interface {
}
}
} else {
let entries = self
.methods
.iter()
.map(|m| {
let name = &m.name;
quote!(#name: #name::<Impl>)
})
.collect::<Vec<_>>();

quote! {
#[repr(C)]
#[doc(hidden)]
Expand Down
Loading
Loading