How do I implement an existing COM interface?
In some cases, you may need to implement an existing COM interface rather than simply calling an existing implementation provided by the operating system. This is where the implement
feature and macro come in handy. The windows crate provides optional implementation support hidden behind the implement
feature. Once enabled, the implement macro may be used to implement any number of COM interfaces. The macro takes care of implementing IUnknown
itself.
Let's implement a simple interface defined by Windows to illustrate. The IPersist
interface is defined in the Win32::System::Com
module, so we'll start by adding a dependency on the windows
crate and include the Win32_System_Com
feature:
[dependencies.windows]
version = "0.52"
features = [
"implement",
"Win32_System_Com",
]
The implement
feature unlocks the implementation support.
The implement
macro is included by the windows::core
module so we'll keep things simple by including it all as follows:
#![allow(unused)] fn main() { use windows::{core::*, Win32::System::Com::*}; }
Now its time for the implementation:
#![allow(unused)] fn main() { #[implement(IPersist)] struct Persist(GUID); }
The implement
macro will provide the necessary implementation for the IUnknown
interface's lifetime management and interface discovery for whatever interfaces are included in the attribute. In this case, only IPersist
is to be implemented.
The implementation itself is defined by a trait that follows the <interface name>_Impl
pattern and its up to us to implement it for our implementation as follows:
#![allow(unused)] fn main() { impl IPersist_Impl for Persist { fn GetClassID(&self) -> Result<GUID> { Ok(self.0) } } }
The IPersist interface, originally documented here, has a single method that returns a GUID
, so we'll just implement it by returning the value contained within our implementation. The window
crate and implement
macro will take care of the rest by providing the actual COM virtual function call and virtual function table layout needed to turn this into a heap-allocated and reference-counted COM object.
All that remains is to move, or box, the implementation into the COM implementation provided by the implement
macro through the Into
trait:
#![allow(unused)] fn main() { let guid = GUID::new()?; let persist: IPersist = Persist(guid).into(); }
At this point, we can simply treat persist
as the COM object that it is:
#![allow(unused)] fn main() { let guid2 = unsafe { persist.GetClassID()? }; assert_eq!(guid, guid2); println!("{:?}", guid); }
Here's a complete example.