Skip to content

Commit

Permalink
basic implementation of impl-on-outer
Browse files Browse the repository at this point in the history
  • Loading branch information
Arlie Davis committed Jun 3, 2024
1 parent 8b4a185 commit ccf95f8
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 53 deletions.
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
54 changes: 54 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,57 @@ 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<Outer> {
/// The type that implements the COM interface.
type Impl;

/// At runtime, casts from the outer object type to the implementation type.
fn get_impl(outer: &Outer) -> &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<Outer> {
_marker: core::marker::PhantomData<Outer>,
}

impl<Outer> ComGetImpl<Outer> for ComGetImplInner<Outer>
where
Outer: IUnknownImpl,
{
type Impl = <Outer as IUnknownImpl>::Impl;

fn get_impl(outer: &Outer) -> &Self::Impl {
<Outer as IUnknownImpl>::get_impl(outer)
}
}

/// 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<Outer> {
_marker: core::marker::PhantomData<Outer>,
}

impl<Outer> ComGetImpl<Outer> for ComGetImplOuter<Outer> {
type Impl = Outer;

fn get_impl(outer: &Outer) -> &Self::Impl {
outer
}
}
95 changes: 73 additions & 22 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<#impl_ident #generics>)
}
ImplLocation::Inner => {
quote!(::windows_core::ComGetImplInner<#impl_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 All @@ -415,19 +435,24 @@ struct ImplementAttributes {
impl syn::parse::Parse for ImplementAttributes {
fn parse(cursor: syn::parse::ParseStream<'_>) -> syn::parse::Result<Self> {
let mut input = Self::default();
let mut current_impl_location = ImplLocation::Inner;

while !cursor.is_empty() {
input.parse_implement(cursor)?;
input.parse_implement(&mut current_impl_location, cursor)?;
}

Ok(input)
}
}

impl ImplementAttributes {
fn parse_implement(&mut self, cursor: syn::parse::ParseStream<'_>) -> syn::parse::Result<()> {
fn parse_implement(
&mut self,
current_impl_location: &mut ImplLocation,
cursor: syn::parse::ParseStream<'_>,
) -> syn::parse::Result<()> {
let tree = cursor.parse::<UseTree2>()?;
self.walk_implement(&tree, &mut String::new())?;
self.walk_implement(&tree, current_impl_location, &mut String::new())?;

if !cursor.is_empty() {
cursor.parse::<syn::Token![,]>()?;
Expand All @@ -439,6 +464,7 @@ impl ImplementAttributes {
fn walk_implement(
&mut self,
tree: &UseTree2,
current_impl_location: &mut ImplLocation,
namespace: &mut String,
) -> syn::parse::Result<()> {
match tree {
Expand All @@ -448,17 +474,21 @@ impl ImplementAttributes {
}

namespace.push_str(&input.ident.to_string());
self.walk_implement(&input.tree, namespace)?;
self.walk_implement(&input.tree, current_impl_location, namespace)?;
}
UseTree2::Name(_) => {
self.implement.push(tree.to_element_type(namespace)?);
self.implement
.push(tree.to_element_type(*current_impl_location, namespace)?);
}
UseTree2::Group(input) => {
for tree in &input.items {
self.walk_implement(tree, namespace)?;
self.walk_implement(tree, current_impl_location, namespace)?;
}
}
UseTree2::TrustLevel(input) => self.trust_level = *input,
UseTree2::ImplLocation(location) => {
*current_impl_location = *location;
}
}

Ok(())
Expand All @@ -470,18 +500,23 @@ enum UseTree2 {
Name(UseName2),
Group(UseGroup2),
TrustLevel(usize),
ImplLocation(ImplLocation),
}

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 +528,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 Down Expand Up @@ -538,23 +574,38 @@ impl syn::parse::Parse for UseTree2 {
tree: Box::new(input.parse()?),
}))
} else if input.peek(syn::Token![=]) {
if ident != "TrustLevel" {
if ident == "TrustLevel" {
input.parse::<syn::Token![=]>()?;
let span = input.span();
let value = input.call(syn::Ident::parse_any)?;
match value.to_string().as_str() {
"Partial" => Ok(UseTree2::TrustLevel(1)),
"Full" => Ok(UseTree2::TrustLevel(2)),
_ => Err(syn::parse::Error::new(
span,
"`TrustLevel` must be `Partial` or `Full`",
)),
}
} else if ident == "ImplLocation" {
input.parse::<syn::Token![=]>()?;
let span = input.span();
let value = input.call(syn::Ident::parse_any)?;
Ok(UseTree2::ImplLocation(match value.to_string().as_str() {
"Outer" => ImplLocation::Outer,
"Inner" => ImplLocation::Inner,
_ => {
return Err(syn::parse::Error::new(
span,
"`ImplLocation` must be `Outer` or `Inner`",
))
}
}))
} else {
return Err(syn::parse::Error::new(
ident.span(),
"Unrecognized key-value pair",
));
}
input.parse::<syn::Token![=]>()?;
let span = input.span();
let value = input.call(syn::Ident::parse_any)?;
match value.to_string().as_str() {
"Partial" => Ok(UseTree2::TrustLevel(1)),
"Full" => Ok(UseTree2::TrustLevel(2)),
_ => Err(syn::parse::Error::new(
span,
"`TrustLevel` must be `Partial` or `Full`",
)),
}
} else {
let generics = if input.peek(syn::Token![<]) {
input.parse::<syn::Token![<]>()?;
Expand Down
Loading

0 comments on commit ccf95f8

Please sign in to comment.