Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  lib.rs   Sprache: unbekannt

 
/*!
Learn more about Rust for Windows here: <https://github.com/microsoft/windows-rs>
*/

use quote::quote;
use syn::spanned::Spanned;

/// Defines a COM interface to call or implement.
///
/// # Example
/// ```rust,ignore
/// #[interface("094d70d6-5202-44b8-abb8-43860da5aca2")]
/// unsafe trait IValue: IUnknown {
///     fn GetValue(&self, value: *mut i32) -> HRESULT;
/// }
///
/// #[implement(IValue)]
/// struct Value(i32);
///
/// impl IValue_Impl for Value {
///     unsafe fn GetValue(&self, value: *mut i32) -> HRESULT {
///         *value = self.0;
///         HRESULT(0)
///     }
/// }
///
/// fn main() {
///     let object: IValue = Value(123).into();
///     // Call interface methods...
/// }
/// ```
#[proc_macro_attribute]
pub fn interface(
    attributes: proc_macro::TokenStream,
    original_type: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let guid = syn::parse_macro_input!(attributes as Guid);
    let interface = syn::parse_macro_input!(original_type as Interface);
    let tokens = match interface.gen_tokens(&guid) {
        Ok(t) => t,
        Err(e) => return e.to_compile_error().into(),
    };
    tokens.into()
}

macro_rules! bail {
    ($item:expr, $($msg:tt),*) => {
        return Err(syn::Error::new($item.span(), std::fmt::format(format_args!($($msg),*))));
    };

}

macro_rules! unexpected_token {
    ($item:expr, $msg:expr) => {
        if let Some(i) = $item {
            bail!(i, "unexpected {}", $msg);
        }
    };
}
macro_rules! expected_token {
    ($sig:tt.$item:tt(), $msg:expr) => {
        if let None = $sig.$item() {
            bail!($sig, "expected {}", $msg);
        }
    };
}

/// Parsed interface
///
/// ```rust,ignore
/// #[windows_interface::interface("8CEEB155-2849-4ce5-9448-91FF70E1E4D9")]
/// unsafe trait IUIAnimationVariable: IUnknown {
/// //^ parses this
///     fn GetValue(&self, value: *mut f64) -> HRESULT;
/// }
/// ```
struct Interface {
    visibility: syn::Visibility,
    name: syn::Ident,
    parent: Option<syn::Path>,
    methods: Vec<InterfaceMethod>,
    docs: Vec<syn::Attribute>,
}

impl Interface {
    /// Generates all the code needed for a COM interface
    fn gen_tokens(&self, guid: &Guid) -> syn::Result<proc_macro2::TokenStream> {
        let vis = &self.visibility;
        let name = &self.name;
        let docs = &self.docs;
        let parent = self.parent_type();
        let vtable_name = quote::format_ident!("{}_Vtbl", name);
        let guid = guid.to_tokens()?;
        let implementation = self.gen_implementation();
        let com_trait = self.get_com_trait();
        let vtable = self.gen_vtable(&vtable_name);
        let conversions = self.gen_conversions();

        Ok(quote! {
            #[repr(transparent)]
            #(#docs)*
            #vis struct #name(#parent);
            #implementation
            unsafe impl ::windows_core::Interface for #name {
                type Vtable = #vtable_name;
                const IID: ::windows_core::GUID = #guid;
            }
            impl ::windows_core::RuntimeName for #name {}
            impl ::core::ops::Deref for #name {
                type Target = #parent;
                fn deref(&self) -> &Self::Target {
                    unsafe { ::core::mem::transmute(self) }
                }
            }
            #com_trait
            #vtable
            #conversions
        })
    }

    /// Generates the methods users can call on the COM interface pointer
    fn gen_implementation(&self) -> proc_macro2::TokenStream {
        let name = &self.name;
        let methods = self
            .methods
            .iter()
            .map(|m| {
                let vis = &m.visibility;
                let name = &m.name;

                let generics = m.gen_consume_generics();
                let params = m.gen_consume_params();
                let args = m.gen_consume_args();
                let ret = &m.ret;

                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),*)
                        }
                    }
                }
            })
            .collect::<Vec<_>>();
        quote! {
            impl #name {
                #(#methods)*
            }
        }
    }

    fn get_com_trait(&self) -> proc_macro2::TokenStream {
        let name = quote::format_ident!("{}_Impl", self.name);
        let vis = &self.visibility;
        let methods = self
            .methods
            .iter()
            .map(|m| {
                let name = &m.name;
                let docs = &m.docs;
                let args = m.gen_args();
                let ret = &m.ret;
                quote! {
                    #(#docs)*
                    unsafe fn #name(&self, #(#args),*) #ret;
                }
            })
            .collect::<Vec<_>>();
        let parent = self.parent_trait_constraint();

        quote! {
            #[allow(non_camel_case_types)]
            #vis trait #name: Sized + #parent {
                #(#methods)*
            }
        }
    }

    /// Generates the vtable for a COM interface
    fn gen_vtable(&self, vtable_name: &syn::Ident) -> proc_macro2::TokenStream {
        let vis = &self.visibility;
        let name = &self.name;
        let trait_name = quote::format_ident!("{}_Impl", name);
        let implvtbl_name = quote::format_ident!("{}_ImplVtbl", name);

        let vtable_entries = self
            .methods
            .iter()
            .map(|m| {
                let name = &m.name;
                let ret = &m.ret;
                let args = m.gen_args();

                if m.is_result() {
                    quote! {
                        pub #name: unsafe extern "system" fn(this: *mut ::core::ffi::c_void, #(#args),*) -> ::windows_core::HRESULT,
                    }
                } else {
                    quote! {
                        pub #name: unsafe extern "system" fn(this: *mut ::core::ffi::c_void, #(#args),*) #ret,
                    }
                }
            })
            .collect::<Vec<_>>();

        let parent_vtable_generics = quote!(Identity, OFFSET);
        let parent_vtable = self.parent_vtable();

        // or_parent_matches will be `|| parent::matches(iid)` if this interface inherits from another
        // interface (except for IUnknown) or will be empty if this is not applicable. This is what allows
        // QueryInterface to work correctly for all interfaces in an inheritance chain, e.g.
        // IFoo3 derives from IFoo2 derives from IFoo.
        //
        // We avoid matching IUnknown because object identity depends on the uniqueness of the IUnknown pointer.
        let or_parent_matches = match parent_vtable.as_ref() {
            Some(parent) if !self.parent_is_iunknown() => quote! (|| <#parent>::matches(iid)),
            _ => quote!(),
        };

        let functions = self
            .methods
            .iter()
            .map(|m| {
                let name = &m.name;
                let args = m.gen_args();
                let params = &m
                    .args
                    .iter()
                    .map(|a| {
                        let pat = &a.pat;
                        quote! { #pat }
                    })
                    .collect::<Vec<_>>();
                let ret = &m.ret;

                let ret = if m.is_result() {
                    quote! { -> ::windows_core::HRESULT }
                } else {
                    quote! { #ret }
                };

                if parent_vtable.is_some() {
                    quote! {
                        unsafe extern "system" fn #name<
                            Identity: ::windows_core::IUnknownImpl,
                            const OFFSET: isize
                        >(
                            this: *mut ::core::ffi::c_void, // <-- This is the COM "this" pointer, which is not the same as &T&nbsp;or &T_Impl.
                            #(#args),*
                        ) #ret
                        where
                            Identity : #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);

                            // 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.
                            <Identity as #trait_name>::#name(this_outer, #(#params),*).into()
                        }
                    }
                } else {
                    quote! {
                        unsafe extern "system" fn #name<Impl: #trait_name>(this: *mut ::core::ffi::c_void, #(#args),*) #ret {
                            let this = (this as *mut *mut ::core::ffi::c_void) as *const ::windows_core::ScopedHeap;
                            let this = (*this).this as *const Impl;
                            (*this).#name(#(#params),*).into()
                        }
                    }
                }
            })
            .collect::<Vec<_>>();

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

            quote! {
                #[repr(C)]
                #[doc(hidden)]
                #vis struct #vtable_name {
                    pub base__: #parent_vtable,
                    #(#vtable_entries)*
                }
                impl #vtable_name {
                    pub const fn new<
                        Identity: ::windows_core::IUnknownImpl,
                        const OFFSET: isize,
                    >() -> Self
                    where
                        Identity : #trait_name
                    {
                        #(#functions)*
                        Self { base__: #parent_vtable::new::<#parent_vtable_generics>(), #(#entries),* }
                    }

                    #[inline(always)]
                    pub fn matches(iid: &::windows_core::GUID) -> bool {
                        *iid == <#name as ::windows_core::Interface>::IID
                        #or_parent_matches
                    }
                }
            }
        } else {
            let entries = self
                .methods
                .iter()
                .map(|m| {
                    let name = &m.name;
                    quote!(#name: #name::<Impl>)
                })
                .collect::<Vec<_>>();

            quote! {
                #[repr(C)]
                #[doc(hidden)]
                #vis struct #vtable_name {
                    #(#vtable_entries)*
                }
                impl #vtable_name {
                    pub const fn new<Impl: #trait_name>() -> Self {
                        #(#functions)*
                        Self { #(#entries),* }
                    }
                }
                struct #implvtbl_name<T: #trait_name> (::core::marker::PhantomData<T>);
                impl<T: #trait_name> #implvtbl_name<T> {
                    const VTABLE: #vtable_name = #vtable_name::new::<T>();
                }
                impl #name {
                    fn new<'a, T: #trait_name>(this: &'a T) -> ::windows_core::ScopedInterface<'a, #name> {
                        let this = ::windows_core::ScopedHeap { vtable: &#implvtbl_name::<T>::VTABLE as *const _ as *const _, this: this as *const _ as *const _ };
                        let this = ::core::mem::ManuallyDrop::new(::windows_core::imp::Box::new(this));
                        unsafe { ::windows_core::ScopedInterface::new(::core::mem::transmute(&this.vtable)) }
                    }
                }
            }
        }
    }

    /// Generates various conversions such as from and to `IUnknown`
    fn gen_conversions(&self) -> proc_macro2::TokenStream {
        let name = &self.name;
        let name_string = format!("{name}");
        quote! {
            impl ::core::convert::From<#name> for ::windows_core::IUnknown {
                fn from(value: #name) -> Self {
                    unsafe { ::core::mem::transmute(value) }
                }
            }
            impl ::core::convert::From<&#name> for ::windows_core::IUnknown {
                fn from(value: &#name) -> Self {
                    ::core::convert::From::from(::core::clone::Clone::clone(value))
                }
            }
            impl ::core::clone::Clone for #name {
                fn clone(&self) -> Self {
                    Self(self.0.clone())
                }
            }
            impl ::core::cmp::PartialEq for #name {
                fn eq(&self, other: &Self) -> bool {
                    self.0 == other.0
                }
            }
            impl ::core::cmp::Eq for #name {}
            impl ::core::fmt::Debug for #name {
                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                    f.debug_tuple(#name_string).field(&::windows_core::Interface::as_raw(self)).finish()
                }
            }
        }
    }

    fn parent_type(&self) -> proc_macro2::TokenStream {
        if let Some(parent) = &self.parent {
            quote!(#parent)
        } else {
            quote!(::core::ptr::NonNull<::core::ffi::c_void>)
        }
    }

    fn parent_vtable(&self) -> Option<proc_macro2::TokenStream> {
        if let Some((ident, path)) = self.parent_path().split_last() {
            let ident = quote::format_ident!("{}_Vtbl", ident);
            Some(quote! { #(#path::)* #ident })
        } else {
            None
        }
    }

    fn parent_is_iunknown(&self) -> bool {
        if let Some(ident) = self.parent_path().last() {
            ident == "IUnknown"
        } else {
            false
        }
    }

    fn parent_path(&self) -> Vec<syn::Ident> {
        if let Some(parent) = &self.parent {
            parent
                .segments
                .iter()
                .map(|segment| segment.ident.clone())
                .collect()
        } else {
            vec![]
        }
    }

    /// Gets the parent trait constrait which is nothing if the parent is IUnknown
    fn parent_trait_constraint(&self) -> proc_macro2::TokenStream {
        if let Some((ident, path)) = self.parent_path().split_last() {
            if ident != "IUnknown" {
                let ident = quote::format_ident!("{}_Impl", ident);
                return quote! { #(#path::)* #ident };
            }
        }

        quote! {}
    }
}

impl syn::parse::Parse for Interface {
    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let attributes = input.call(syn::Attribute::parse_outer)?;
        let mut docs = Vec::new();
        for attr in attributes.into_iter() {
            let path = attr.path();
            if path.is_ident("doc") {
                docs.push(attr);
            } else {
                return Err(syn::Error::new(path.span(), "Unrecognized attribute "));
            }
        }

        let visibility = input.parse::<syn::Visibility>()?;
        _ = input.parse::<syn::Token![unsafe]>()?;
        _ = input.parse::<syn::Token![trait]>()?;
        let name = input.parse::<syn::Ident>()?;
        _ = input.parse::<syn::Token![:]>();
        let parent = input.parse::<syn::Path>().ok();
        let content;
        syn::braced!(content in input);
        let mut methods = Vec::new();
        while !content.is_empty() {
            methods.push(content.parse::<InterfaceMethod>()?);
        }
        Ok(Self {
            visibility,
            methods,
            name,
            parent,
            docs,
        })
    }
}

/// Parsed interface guid attribute
///
/// ```rust,ignore
/// #[windows_interface::interface("8CEEB155-2849-4ce5-9448-91FF70E1E4D9")]
///                              //^ parses this
/// unsafe trait IUIAnimationVariable: IUnknown {
///     fn GetValue(&self, value: *mut f64) -> HRESULT;
/// }
/// ```
struct Guid(Option<syn::LitStr>);

impl Guid {
    fn to_tokens(&self) -> syn::Result<proc_macro2::TokenStream> {
        fn hex_lit(num: &str) -> syn::LitInt {
            syn::LitInt::new(&format!("0x{num}"), proc_macro2::Span::call_site())
        }

        fn ensure_length(
            part: Option<&str>,
            index: usize,
            length: usize,
            span: proc_macro2::Span,
        ) -> syn::Result<String> {
            let part = match part {
                Some(p) => p,
                None => {
                    return Err(syn::Error::new(
                        span,
                        format!("The IID missing part at index {index}"),
                    ))
                }
            };

            if part.len() != length {
                return Err(syn::Error::new(
                    span,
                    format!(
                        "The IID part at index {} must be {} characters long but was {} characters",
                        index,
                        length,
                        part.len()
                    ),
                ));
            }

            Ok(part.to_owned())
        }

        if let Some(value) = &self.0 {
            let guid_value = value.value();
            let mut delimited = guid_value.split('-').fuse();
            let chunks = [
                ensure_length(delimited.next(), 0, 8, value.span())?,
                ensure_length(delimited.next(), 1, 4, value.span())?,
                ensure_length(delimited.next(), 2, 4, value.span())?,
                ensure_length(delimited.next(), 3, 4, value.span())?,
                ensure_length(delimited.next(), 4, 12, value.span())?,
            ];

            let data1 = hex_lit(&chunks[0]);
            let data2 = hex_lit(&chunks[1]);
            let data3 = hex_lit(&chunks[2]);
            let (data4_1, data4_2) = chunks[3].split_at(2);
            let data4_1 = hex_lit(data4_1);
            let data4_2 = hex_lit(data4_2);
            let (data4_3, rest) = chunks[4].split_at(2);
            let data4_3 = hex_lit(data4_3);

            let (data4_4, rest) = rest.split_at(2);
            let data4_4 = hex_lit(data4_4);

            let (data4_5, rest) = rest.split_at(2);
            let data4_5 = hex_lit(data4_5);

            let (data4_6, rest) = rest.split_at(2);
            let data4_6 = hex_lit(data4_6);

            let (data4_7, data4_8) = rest.split_at(2);
            let data4_7 = hex_lit(data4_7);
            let data4_8 = hex_lit(data4_8);
            Ok(quote! {
                ::windows_core::GUID {
                    data1: #data1,
                    data2: #data2,
                    data3: #data3,
                    data4: [#data4_1, #data4_2, #data4_3, #data4_4, #data4_5, #data4_6, #data4_7, #data4_8]
                }
            })
        } else {
            Ok(quote! {
                ::windows_core::GUID::zeroed()
            })
        }
    }
}

impl syn::parse::Parse for Guid {
    fn parse(cursor: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let string: Option<syn::LitStr> = cursor.parse().ok();

        Ok(Self(string))
    }
}

/// A parsed interface method
///
/// ```rust,ignore
/// #[windows_interface::interface("8CEEB155-2849-4ce5-9448-91FF70E1E4D9")]
/// unsafe trait IUIAnimationVariable: IUnknown {
///     fn GetValue(&self, value: *mut f64) -> HRESULT;
///   //^ parses this
/// }
/// ```
struct InterfaceMethod {
    pub name: syn::Ident,
    pub visibility: syn::Visibility,
    pub args: Vec<InterfaceMethodArg>,
    pub ret: syn::ReturnType,
    pub docs: Vec<syn::Attribute>,
}

impl InterfaceMethod {
    fn is_result(&self) -> bool {
        if let syn::ReturnType::Type(_, ty) = &self.ret {
            if let syn::Type::Path(path) = &**ty {
                if let Some(segment) = path.path.segments.last() {
                    let ident = segment.ident.to_string();
                    if ident == "Result" {
                        if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
                            if args.args.len() == 1 {
                                return true;
                            }
                        }
                    }
                }
            }
        }

        false
    }

    /// Generates arguments (of the form `$pat: $type`)
    fn gen_args(&self) -> Vec<proc_macro2::TokenStream> {
        self.args
            .iter()
            .map(|a| {
                let pat = &a.pat;
                let ty = &a.ty;
                quote! { #pat: #ty }
            })
            .collect::<Vec<_>>()
    }

    fn gen_consume_generics(&self) -> Vec<proc_macro2::TokenStream> {
        self.args
            .iter()
            .enumerate()
            .filter_map(|(generic_index, a)| {
                if let Some((ty, ident)) = a.borrow_type() {
                    let generic_ident = quote::format_ident!("P{generic_index}");
                    if ident == "Ref" {
                        Some(quote! { #generic_ident: ::windows_core::Param<#ty> })
                    } else {
                        Some(quote! { #generic_ident: ::windows_core::OutParam<#ty> })
                    }
                } else {
                    None
                }
            })
            .collect::<Vec<_>>()
    }

    fn gen_consume_params(&self) -> Vec<proc_macro2::TokenStream> {
        self.args
            .iter()
            .enumerate()
            .map(|(generic_index, a)| {
                let pat = &a.pat;

                if a.borrow_type().is_some() {
                    let generic_ident = quote::format_ident!("P{generic_index}");
                    quote! { #pat: #generic_ident }
                } else {
                    let ty = &a.ty;
                    quote! { #pat: #ty }
                }
            })
            .collect::<Vec<_>>()
    }

    fn gen_consume_args(&self) -> Vec<proc_macro2::TokenStream> {
        self.args
            .iter()
            .map(|a| {
                let pat = &a.pat;

                if let Some((_, ident)) = a.borrow_type() {
                    if ident == "Ref" {
                        quote! { #pat.param().borrow() }
                    } else {
                        quote! { #pat.borrow_mut() }
                    }
                } else {
                    quote! { #pat }
                }
            })
            .collect::<Vec<_>>()
    }
}

impl syn::parse::Parse for InterfaceMethod {
    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let docs = input.call(syn::Attribute::parse_outer)?;
        let visibility = input.parse::<syn::Visibility>()?;
        let method = input.parse::<syn::TraitItemFn>()?;
        unexpected_token!(docs.iter().find(|a| !a.path().is_ident("doc")), "attribute");
        unexpected_token!(method.default, "default method implementation");
        let sig = method.sig;
        unexpected_token!(sig.abi, "abi declaration");
        unexpected_token!(sig.asyncness, "async declaration");
        unexpected_token!(sig.generics.params.iter().next(), "generics declaration");
        unexpected_token!(sig.constness, "const declaration");
        expected_token!(
            sig.receiver(),
            "the method to have &self as its first argument"
        );
        unexpected_token!(sig.variadic, "variadic args");
        let args = sig
            .inputs
            .into_iter()
            .filter_map(|a| match a {
                syn::FnArg::Receiver(_) => None,
                syn::FnArg::Typed(p) => Some(p),
            })
            .map(|p| {
                Ok(InterfaceMethodArg {
                    ty: p.ty,
                    pat: p.pat,
                })
            })
            .collect::<Result<Vec<InterfaceMethodArg>, syn::Error>>()?;

        let ret = sig.output;
        Ok(InterfaceMethod {
            name: sig.ident,
            visibility,
            args,
            ret,
            docs,
        })
    }
}

/// An argument to an interface method
struct InterfaceMethodArg {
    /// The type of the argument
    pub ty: Box<syn::Type>,
    /// The name of the argument
    pub pat: Box<syn::Pat>,
}

impl InterfaceMethodArg {
    fn borrow_type(&self) -> Option<(syn::Type, String)> {
        if let syn::Type::Path(path) = &*self.ty {
            if let Some(segment) = path.path.segments.last() {
                let ident = segment.ident.to_string();
                if matches!(ident.as_str(), "Ref" | "OutRef") {
                    if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
                        if args.args.len() == 1 {
                            if let Some(syn::GenericArgument::Type(ty)) = args.args.first() {
                                return Some((ty.clone(), ident));
                            }
                        }
                    }
                }
            }
        }

        None
    }
}

[ Dauer der Verarbeitung: 0.5 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge