diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 9549606abc..1b46cf8873 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -22,7 +22,7 @@ pub fn import(stream: TokenStream) -> TokenStream { let stage = TypeStage::from_limits(reader, &limits); let tree = stage.into_tree(); let stream = tree.to_tokens(); - + //std::fs::write(r"c:\git\rust\dump.rs", stream.to_string()).unwrap(); stream.into() } diff --git a/crates/winmd/src/lib.rs b/crates/winmd/src/lib.rs index 600ad53f46..9e29872aaa 100644 --- a/crates/winmd/src/lib.rs +++ b/crates/winmd/src/lib.rs @@ -25,10 +25,6 @@ fn format_ident(name: &str) -> proc_macro2::Ident { } } -fn format_abi_ident(name: &str) -> proc_macro2::Ident { - quote::format_ident!("abi_{}", name) -} - #[cfg(target_pointer_width = "64")] const SYSTEM32: &str = "System32"; diff --git a/crates/winmd/src/types/delegate.rs b/crates/winmd/src/types/delegate.rs index 8fb970e35d..eaeca43012 100644 --- a/crates/winmd/src/types/delegate.rs +++ b/crates/winmd/src/types/delegate.rs @@ -31,19 +31,38 @@ impl Delegate { pub fn to_tokens(&self) -> TokenStream { let definition = self.name.to_definition_tokens(&self.name.namespace); let abi_definition = self.name.to_abi_definition_tokens(&self.name.namespace); + let fn_constraint = self.to_fn_constraint_tokens(); + let impl_definition = self.to_impl_definition_tokens(&fn_constraint); let name = self.name.to_tokens(&self.name.namespace); + let abi_name = self.name.to_abi_tokens(&self.name.namespace); + let impl_name = self.to_impl_name_tokens(); let phantoms = self.name.phantoms(); let constraints = self.name.constraints(); + let method = self.method.to_default_tokens(&self.name.namespace); let abi_method = self.method.to_abi_tokens(&self.name, &self.name.namespace); let guid = self.guid.to_tokens(); + let invoke_sig = self + .method + .to_abi_impl_tokens(&self.name, &self.name.namespace); + let invoke_args = self + .method + .params + .iter() + .map(|param| param.to_invoke_arg_tokens()); quote! { #[repr(transparent)] #[derive(Default)] pub struct #definition where #constraints { - ptr: ::winrt::IUnknown, + ptr: ::winrt::ComPtr<#name>, #phantoms } + impl<#constraints> #name { + #method + pub fn new<#fn_constraint>(invoke: F) -> Self { + #impl_name::new(invoke) + } + } unsafe impl<#constraints> ::winrt::ComInterface for #name { type VTable = #abi_definition; const IID: ::winrt::Guid = ::winrt::Guid::from_values(#guid); @@ -58,19 +77,143 @@ impl Delegate { } #[repr(C)] pub struct #abi_definition where #constraints { - __base: [usize; 6], + pub unknown_query_interface: extern "system" fn(::winrt::RawComPtr<::winrt::IUnknown>, &::winrt::Guid, *mut ::winrt::RawPtr) -> ::winrt::ErrorCode, + pub unknown_add_ref: extern "system" fn(::winrt::RawComPtr<::winrt::IUnknown>) -> u32, + pub unknown_release: extern "system" fn(::winrt::RawComPtr<::winrt::IUnknown>) -> u32, #abi_method #phantoms } unsafe impl<#constraints> ::winrt::RuntimeType for #name { - type Abi = ::winrt::RawPtr; + type Abi = ::winrt::RawComPtr; fn abi(&self) -> Self::Abi { - <::winrt::IUnknown as ::winrt::ComInterface>::as_raw(&self.ptr) as Self::Abi + <::winrt::ComPtr as ::winrt::ComInterface>::as_raw(&self.ptr) } fn set_abi(&mut self) -> *mut Self::Abi { - self.ptr.set_abi() as _ + self.ptr.set_abi() + } + } + #[repr(C)] + struct #impl_definition where #constraints { + vtable: *const #abi_definition, + count: ::winrt::RefCount, + invoke: F, + } + impl<#constraints #fn_constraint> #impl_name { + const VTABLE: #abi_definition = #abi_name { + unknown_query_interface: #impl_name::unknown_query_interface, + unknown_add_ref: #impl_name::unknown_add_ref, + unknown_release: #impl_name::unknown_release, + invoke: #impl_name::invoke, + #phantoms + }; + pub fn new(invoke: F) -> #name { + let value = Self { + vtable: &Self::VTABLE, + count: ::winrt::RefCount::new(1), + invoke, + }; + unsafe { + let mut result: #name = std::mem::zeroed(); + *<#name as ::winrt::RuntimeType>::set_abi(&mut result) = ::std::boxed::Box::into_raw(::std::boxed::Box::new(value)) as *const *const #abi_definition; + result + } + } + extern "system" fn unknown_query_interface( + this: ::winrt::RawComPtr<::winrt::IUnknown>, + iid: &::winrt::Guid, + interface: *mut ::winrt::RawPtr, + ) -> ::winrt::ErrorCode { + unsafe { + let this = this as *const Self as *mut Self; + + if *iid == <#name as ::winrt::ComInterface>::IID + || *iid == <::winrt::IUnknown as ::winrt::ComInterface>::IID + || *iid == <::winrt::IAgileObject as ::winrt::ComInterface>::IID + { + *interface = this as ::winrt::RawPtr; + (*this).count.add_ref(); + return ::winrt::ErrorCode(0); + } + + *interface = std::ptr::null_mut(); + ::winrt::ErrorCode(0x80004002) + } + } + extern "system" fn unknown_add_ref(this: ::winrt::RawComPtr<::winrt::IUnknown>) -> u32 { + unsafe { + let this = this as *const Self as *mut Self; + (*this).count.add_ref() + } + } + extern "system" fn unknown_release(this: ::winrt::RawComPtr<::winrt::IUnknown>) -> u32 { + unsafe { + let this = this as *const Self as *mut Self; + let remaining = (*this).count.release(); + + if remaining == 0 { + Box::from_raw(this); + } + + remaining + } + } + #invoke_sig { + unsafe { + let this = this as *const Self as *mut Self; + ((*this).invoke)(#(#invoke_args,)*).into() + } } } } } + + fn to_fn_constraint_tokens(&self) -> TokenStream { + let params = self + .method + .params + .iter() + .map(|param| param.to_fn_tokens(&self.name.namespace)); + + let return_type = if let Some(return_type) = &self.method.return_type { + return_type.to_return_tokens(&self.name.namespace) + } else { + quote! { () } + }; + + quote! { F: FnMut(#(#params)*) -> ::winrt::Result<#return_type> } + } + + fn to_impl_definition_tokens(&self, fn_constraint: &TokenStream) -> TokenStream { + if self.name.generics.is_empty() { + let name = format_impl_ident(&self.name.name); + quote! { #name<#fn_constraint> } + } else { + let name = format_impl_ident(&self.name.name[..self.name.name.len() - 2]); + let generics = self + .name + .generics + .iter() + .map(|g| g.to_tokens(&self.name.namespace)); + quote! { #name<#(#generics,)* #fn_constraint> } + } + } + + fn to_impl_name_tokens(&self) -> TokenStream { + if self.name.generics.is_empty() { + let name = format_impl_ident(&self.name.name); + quote! { #name:: } + } else { + let name = format_impl_ident(&self.name.name[..self.name.name.len() - 2]); + let generics = self + .name + .generics + .iter() + .map(|g| g.to_tokens(&self.name.namespace)); + quote! { #name::<#(#generics,)* F> } + } + } +} + +fn format_impl_ident(name: &str) -> proc_macro2::Ident { + quote::format_ident!("impl_{}", name) } diff --git a/crates/winmd/src/types/interface.rs b/crates/winmd/src/types/interface.rs index 7734816ad3..87403fc33d 100644 --- a/crates/winmd/src/types/interface.rs +++ b/crates/winmd/src/types/interface.rs @@ -85,7 +85,12 @@ impl Interface { } #[repr(C)] pub struct #abi_definition where #constraints { - __base: [usize; 6], + pub unknown_query_interface: extern "system" fn(::winrt::RawComPtr<::winrt::IUnknown>, &::winrt::Guid, *mut ::winrt::RawPtr) -> ::winrt::ErrorCode, + pub unknown_add_ref: extern "system" fn(::winrt::RawComPtr<::winrt::IUnknown>) -> u32, + pub unknown_release: extern "system" fn(::winrt::RawComPtr<::winrt::IUnknown>) -> u32, + pub inspectable_iids: extern "system" fn(::winrt::RawComPtr<::winrt::Object>, *mut u32, *mut *mut ::winrt::Guid) -> ::winrt::ErrorCode, + pub inspectable_type_name: extern "system" fn(::winrt::RawComPtr<::winrt::Object>, *mut <::winrt::HString as ::winrt::RuntimeType>::Abi) -> ::winrt::ErrorCode, + pub inspectable_trust_level: extern "system" fn(::winrt::RawComPtr<::winrt::Object>, *mut i32) -> ::winrt::ErrorCode, #abi_methods #phantoms } diff --git a/crates/winmd/src/types/method.rs b/crates/winmd/src/types/method.rs index e3d3b9f7df..6d57bb7d56 100644 --- a/crates/winmd/src/types/method.rs +++ b/crates/winmd/src/types/method.rs @@ -66,7 +66,7 @@ impl Method { let return_type = if blob.read_expected(0x01) { None } else { - let name = String::new(); + let name = "__result".to_owned(); let array = blob.peek_unsigned().0 == 0x1D; let kind = TypeKind::from_blob(&mut blob, generics); let input = false; @@ -147,6 +147,24 @@ impl Method { } } + pub fn to_abi_impl_tokens(&self, self_name: &TypeName, calling_namespace: &str) -> TokenStream { + let abi_name = self_name.to_abi_tokens(calling_namespace); + let name = format_ident(&self.name); + let params = self + .params + .iter() + .chain(self.return_type.iter()) + .map(|param| { + let name = format_ident(¶m.name); + let abi = param.to_abi_tokens(calling_namespace); + quote! { #name: #abi } + }); + + quote! { + extern "system" fn #name(this: *const *const #abi_name, #(#params)*) -> ::winrt::ErrorCode + } + } + fn to_param_tokens(&self, calling_namespace: &str) -> TokenStream { TokenStream::from_iter( self.params diff --git a/crates/winmd/src/types/param.rs b/crates/winmd/src/types/param.rs index d7336272a7..4af2386b37 100644 --- a/crates/winmd/src/types/param.rs +++ b/crates/winmd/src/types/param.rs @@ -45,6 +45,36 @@ impl Param { } } + pub fn to_fn_tokens(&self, calling_namespace: &str) -> TokenStream { + let tokens = self.kind.to_tokens(calling_namespace); + + if self.array { + if self.input { + quote! { &[#tokens], } + } else if self.by_ref { + quote! { &mut ::winrt::Array<#tokens>, } + } else { + quote! { &mut [#tokens], } + } + } else if self.input { + match self.kind { + TypeKind::String + | TypeKind::Object + | TypeKind::Guid + | TypeKind::Class(_) + | TypeKind::Interface(_) + | TypeKind::Struct(_) + | TypeKind::Delegate(_) + | TypeKind::Generic(_) => { + quote! { &#tokens, } + } + _ => quote! { #tokens, }, + } + } else { + quote! { &mut #tokens, } + } + } + pub fn to_return_tokens(&self, calling_namespace: &str) -> TokenStream { let tokens = self.kind.to_tokens(calling_namespace); @@ -117,4 +147,23 @@ impl Param { quote! { ::winrt::RuntimeType::set_abi(#name), } } } + + pub fn to_invoke_arg_tokens(&self) -> TokenStream { + let name = format_ident(&self.name); + + if self.array { + // TODO: delegate with array parameters are challenging to say the least. + // I'll get to them shortly. + panic!("array"); + } else if self.input { + match self.kind { + TypeKind::Enum(_) => quote! { *::winrt::RuntimeType::from_abi(&#name) }, + _ => quote! { ::winrt::RuntimeType::from_abi(&#name) }, + } + } else if self.kind.blittable() { + quote! { #name, } + } else { + quote! { ::winrt::RuntimeType::from_mut_abi(#name) } + } + } } diff --git a/crates/winmd/src/types/type_kind.rs b/crates/winmd/src/types/type_kind.rs index b27b8fbe40..7493e9f676 100644 --- a/crates/winmd/src/types/type_kind.rs +++ b/crates/winmd/src/types/type_kind.rs @@ -218,32 +218,23 @@ impl TypeKind { Self::U64 => quote! { u64, }, Self::F32 => quote! { f32, }, Self::F64 => quote! { f64, }, + Self::Guid => quote! { ::winrt::Guid, }, Self::String => { quote! { <::winrt::HString as ::winrt::RuntimeType>::Abi, } } Self::Object => { quote! { <::winrt::Object as ::winrt::RuntimeType>::Abi, } } - Self::Guid => quote! { ::winrt::Guid, }, - Self::Class(c) => { - let name = c.to_tokens(calling_namespace); - quote! { <#name as ::winrt::RuntimeType>::Abi, } - } - Self::Interface(i) => { - let name = i.to_tokens(calling_namespace); - quote! { <#name as ::winrt::RuntimeType>::Abi, } - } - Self::Enum(name) => { - let name = name.to_tokens(calling_namespace); + Self::Generic(name) => { + let name = format_ident(name); quote! { <#name as ::winrt::RuntimeType>::Abi, } } - Self::Struct(name) => { + Self::Class(name) + | Self::Interface(name) + | Self::Delegate(name) + | Self::Enum(name) + | Self::Struct(name) => { let name = name.to_tokens(calling_namespace); - quote! { #name, } - } - Self::Delegate(_) => quote! { ::winrt::RawPtr, }, - Self::Generic(name) => { - let name = format_ident(name); quote! { <#name as ::winrt::RuntimeType>::Abi, } } } diff --git a/crates/winmd/src/types/type_name.rs b/crates/winmd/src/types/type_name.rs index b511e4c17f..f3dcc6f678 100644 --- a/crates/winmd/src/types/type_name.rs +++ b/crates/winmd/src/types/type_name.rs @@ -255,27 +255,24 @@ impl TypeName { // and we would simply use to_tokens and to_abi_tokens everywhere but Rust is really // weird in requiring `IVector` in some places and `IVector::` in others. pub fn to_definition_tokens(&self, calling_namespace: &str) -> TokenStream { - let namespace = to_namespace_tokens(&self.namespace, calling_namespace); if self.generics.is_empty() { let name = format_ident(&self.name); - quote! { #namespace#name } + quote! { #name } } else { let name = format_ident(&self.name[..self.name.len() - 2]); let generics = self.generics.iter().map(|g| g.to_tokens(calling_namespace)); - quote! { #namespace#name<#(#generics),*> } + quote! { #name<#(#generics),*> } } } pub fn to_abi_definition_tokens(&self, calling_namespace: &str) -> TokenStream { - let namespace = to_namespace_tokens(&self.namespace, calling_namespace); - if self.generics.is_empty() { let name = format_abi_ident(&self.name); - quote! { #namespace#name } + quote! { #name } } else { let name = format_abi_ident(&self.name[..self.name.len() - 2]); let generics = self.generics.iter().map(|g| g.to_tokens(calling_namespace)); - quote! { #namespace#name<#(#generics),*> } + quote! { #name<#(#generics),*> } } } @@ -303,6 +300,10 @@ impl TypeName { } } +fn format_abi_ident(name: &str) -> proc_macro2::Ident { + quote::format_ident!("abi_{}", name) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/agile_object.rs b/src/agile_object.rs new file mode 100644 index 0000000000..4113e41d93 --- /dev/null +++ b/src/agile_object.rs @@ -0,0 +1,25 @@ +use crate::*; + +#[repr(transparent)] +#[derive(Default, Clone)] +pub struct IAgileObject { + ptr: ComPtr, +} + +unsafe impl ComInterface for IAgileObject { + type VTable = abi_IAgileObject; + const IID: Guid = Guid::from_values( + 0x94EA2B94, + 0xE9CC, + 0x49E0, + [0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90], + ); +} + +#[repr(C)] +pub struct abi_IAgileObject { + pub unknown_query_interface: + extern "system" fn(RawComPtr, &Guid, *mut RawPtr) -> ErrorCode, + pub unknown_add_ref: extern "system" fn(RawComPtr) -> u32, + pub unknown_release: extern "system" fn(RawComPtr) -> u32, +} diff --git a/src/com_ptr.rs b/src/com_ptr.rs index 2b420e27dc..c80fc82bd0 100644 --- a/src/com_ptr.rs +++ b/src/com_ptr.rs @@ -7,6 +7,10 @@ pub struct ComPtr { } impl ComPtr { + pub fn abi(&self) -> RawComPtr { + self.ptr + } + pub fn set_abi(&mut self) -> *mut RawComPtr { if !self.ptr.is_null() { unsafe { diff --git a/src/error.rs b/src/error.rs index 47810a3112..603b8c76bc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,17 @@ #[must_use] pub type Result = std::result::Result; +impl std::convert::From> for ErrorCode { + fn from(result: Result) -> Self { + if let Err(error) = result { + // TODO: call SetErrorInfo + return error.code(); + } + + ErrorCode(0) + } +} + /// A WinRT related error #[derive(Debug)] pub struct Error { diff --git a/src/hstring.rs b/src/hstring.rs index d0e6745b10..2b800fae99 100644 --- a/src/hstring.rs +++ b/src/hstring.rs @@ -230,7 +230,7 @@ impl Header { fn duplicate(&mut self) -> *mut Header { if self.flags & REFERENCE_FLAG == 0 { unsafe { - (*self.shared.as_ptr()).count.addref(); + (*self.shared.as_ptr()).count.add_ref(); self } } else { diff --git a/src/lib.rs b/src/lib.rs index 2b5d81e8b1..d70f34c08e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,7 @@ doc_comment::doctest!("../README.md"); #[doc(hidden)] pub mod activation; +pub mod agile_object; mod array; mod com_interface; mod com_ptr; @@ -58,6 +59,7 @@ mod unknown; #[doc(inline)] pub use activation::IActivationFactory; +pub use agile_object::IAgileObject; pub use array::Array; pub use com_interface::{ComInterface, RawComPtr}; pub use com_ptr::ComPtr; @@ -66,6 +68,7 @@ pub use guid::Guid; pub use hstring::HString; pub use object::Object; pub use param::Param; +pub use ref_count::RefCount; pub use runtime_name::RuntimeName; pub use runtime_type::RuntimeType; pub use try_into::TryInto; diff --git a/src/object.rs b/src/object.rs index c274b11cb5..345d1ac979 100644 --- a/src/object.rs +++ b/src/object.rs @@ -47,7 +47,14 @@ unsafe impl RuntimeType for Object { #[repr(C)] pub struct abi_IInspectable { - __base: [usize; 4], - inspectable_type_name: + pub unknown_query_interface: + extern "system" fn(RawComPtr, &Guid, *mut RawPtr) -> ErrorCode, + pub unknown_add_ref: extern "system" fn(RawComPtr) -> u32, + pub unknown_release: extern "system" fn(RawComPtr) -> u32, + + pub inspectable_iids: + extern "system" fn(RawComPtr, *mut u32, *mut *mut Guid) -> ErrorCode, + pub inspectable_type_name: extern "system" fn(RawComPtr, *mut ::Abi) -> ErrorCode, + pub inspectable_trust_level: extern "system" fn(RawComPtr, *mut i32) -> ErrorCode, } diff --git a/src/ref_count.rs b/src/ref_count.rs index 46220d69fc..c035757431 100644 --- a/src/ref_count.rs +++ b/src/ref_count.rs @@ -12,7 +12,7 @@ impl RefCount { } } - pub fn addref(&self) -> u32 { + pub fn add_ref(&self) -> u32 { self.value.fetch_add(1, Ordering::Relaxed) + 1 } diff --git a/src/runtime_type.rs b/src/runtime_type.rs index 02482b07ce..f4c5c43baf 100644 --- a/src/runtime_type.rs +++ b/src/runtime_type.rs @@ -14,6 +14,14 @@ pub unsafe trait RuntimeType { fn abi(&self) -> Self::Abi; fn set_abi(&mut self) -> *mut Self::Abi; + + fn from_abi(abi: &Self::Abi) -> &Self { + unsafe { std::mem::transmute_copy(&abi) } + } + + fn from_mut_abi(abi: &mut Self::Abi) -> &mut Self { + unsafe { std::mem::transmute_copy(&abi) } + } } macro_rules! primitive_runtime_type { diff --git a/src/unknown.rs b/src/unknown.rs index 55221af089..ac1eed6054 100644 --- a/src/unknown.rs +++ b/src/unknown.rs @@ -25,8 +25,8 @@ unsafe impl ComInterface for IUnknown { #[repr(C)] pub struct abi_IUnknown { - pub(crate) unknown_query_interface: + pub unknown_query_interface: extern "system" fn(RawComPtr, &Guid, *mut RawPtr) -> ErrorCode, - pub(crate) unknown_add_ref: extern "system" fn(RawComPtr) -> u32, - pub(crate) unknown_release: extern "system" fn(RawComPtr) -> u32, + pub unknown_add_ref: extern "system" fn(RawComPtr) -> u32, + pub unknown_release: extern "system" fn(RawComPtr) -> u32, } diff --git a/tests/delegates.rs b/tests/delegates.rs new file mode 100644 index 0000000000..6c2c851fed --- /dev/null +++ b/tests/delegates.rs @@ -0,0 +1,69 @@ +winrt::import!( + dependencies + "os" + modules + "windows.foundation" +); + +use winrt::ComInterface; + +#[test] +fn non_generic() -> winrt::Result<()> { + use windows::foundation::AsyncStatus; + use windows::foundation::IAsyncAction; + type Handler = windows::foundation::AsyncActionCompletedHandler; + + assert_eq!( + Handler::IID, + winrt::Guid::from("A4ED5C81-76C9-40BD-8BE6-B1D90FB20AE7") + ); + + let d = Handler::default(); + assert!(d.is_null()); + + let mut invoked = false; + + let d = Handler::new(|info, status| { + invoked = true; + assert!(info.is_null()); + assert!(status == AsyncStatus::Completed); + Ok(()) + }); + + // TODO: delegates are function objects (logically) ans we should be able + // to call them without an explicit `invoke` method e.g. `d(args);` + d.invoke(IAsyncAction::default(), AsyncStatus::Completed)?; + + assert!(invoked); + + Ok(()) +} + +#[test] +fn generic() -> winrt::Result<()> { + use windows::foundation::Uri; + type Handler = windows::foundation::TypedEventHandler; + + // TODO: Handler::IID is not correct for generic types + + let d = Handler::default(); + assert!(d.is_null()); + + let uri = &Uri::create_uri("http://kennykerr.ca")?; + let mut invoked = false; + + let d = Handler::new(|sender, port| { + invoked = true; + assert!(uri.as_raw() == sender.as_raw()); + + // TODO: ideally primitives would be passed by value + assert!(*port == 80); + Ok(()) + }); + + d.invoke(uri, uri.port()?)?; + + assert!(invoked); + + Ok(()) +}