diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-02 18:36:06 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-02 18:36:06 -0600 |
| commit | 8cdfa445d6629ffef4cb84967ff7017654045bc2 (patch) | |
| tree | 22f0b0907c024c78d26a731e2e1f5219407d8102 /vendor/windows-interface/src | |
| parent | 4351c74c7c5f97156bc94d3a8549b9940ac80e3f (diff) | |
chore: add vendor directory
Diffstat (limited to 'vendor/windows-interface/src')
| -rw-r--r-- | vendor/windows-interface/src/lib.rs | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/vendor/windows-interface/src/lib.rs b/vendor/windows-interface/src/lib.rs new file mode 100644 index 00000000..73c8f40c --- /dev/null +++ b/vendor/windows-interface/src/lib.rs @@ -0,0 +1,762 @@ +//! Define COM interfaces to call or implement. +//! +//! Take a look at [macro@interface] for an example. +//! +//! 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,no_run +/// use windows_core::*; +/// +/// #[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_Impl { +/// unsafe fn GetValue(&self, value: *mut i32) -> HRESULT { +/// *value = self.0; +/// HRESULT(0) +/// } +/// } +/// +/// 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 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 constraint 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 + } +} |
