Search code examples
winapirustinterfacecomwmi

Implement IWbemObjectSink interface in Rust


I'd like to be able to call ExecQueryAsync of SWbemServices in Rust, as in this example in C++ : https://learn.microsoft.com/fr-fr/windows/win32/wmisdk/example--getting-wmi-data-from-the-local-computer-asynchronously

My problem is it needs an implementation of IWbemObjectSink, and none is available in winapi. The winapi crates provides an interface definition in Rust, but how am I supposed to "implement" it in Rust ? I can write a struct that respects the interface, I can import the interface from the winapi crate, but how can I glue both together ?

I also made some experiments with com-rs crate, as it provides an example to implement an interface with the com::class! macro But what I can do is create another IWbemObjectSink interface and implement it, not starting from the winapi::um::wbemcli:IWbemObjectSink and implement it...

Thanks for your insights


Solution

  • If anyone is trying to implement a COM interface of the winapi crate like IWbemObjectSink, for which WMI does not provide an implementation, here is a way to do so.

    DON'T use the com-rs crate maintained by Microsoft as long as they don't add support for the winapi crate. Prefer the com-impl crate, in combination with wio ComPtr (not necessary but handy).

    use winapi::{
        um::wbemcli::{
            {IWbemClassObject,IWbemObjectSink, IWbemObjectSinkVtbl},
            WBEM_S_NO_ERROR,
        },
        shared::{
            ntdef::HRESULT,
            wtypes::BSTR,
        },
        ctypes::{
            c_long,
        },
    };
    use com_impl::{ComImpl, VTable, Refcount};
    use wio::com::ComPtr;
    
    #[repr(C)]
    #[derive(ComImpl)]
    #[interfaces(IWbemObjectSink)]
    pub struct QuerySink {
        vtbl: VTable<IWbemObjectSinkVtbl>,
        refcount: Refcount,
    }
    
    impl QuerySink {
        pub fn new() -> ComPtr<IWbemObjectSink> {
            let ptr = QuerySink::create_raw();
            let ptr = ptr as *mut IWbemObjectSink;
            unsafe { ComPtr::from_raw(ptr) }
        }
    }
    
    // AddRef and Release methods are provided by com_impl
    #[com_impl::com_impl]
    unsafe impl IWbemObjectSink for QuerySink2 {
        pub unsafe fn indicate(
            &self,
            _lObjectCount: c_long,
            _apObjArray: *mut *mut IWbemClassObject
        ) -> HRESULT {
            WBEM_S_NO_ERROR as i32
        }
    
        pub unsafe fn set_status(
            &self,
            _lFlags: c_long,
            _hResult: HRESULT,
            _strParam: BSTR,
            _pObjParam: *mut IWbemClassObject
        ) -> HRESULT {
            WBEM_S_NO_ERROR as i32
        }
    }
    
    

    Some other useful information:

    If you need to add members to your class (here yolo and swag), procede like this:

    #[repr(C)]
    #[derive(ComImpl)]
    #[interfaces(IWbemObjectSink)]
    pub struct QuerySink2 {
        vtbl: VTable<IWbemObjectSinkVtbl>,
        refcount: Refcount,
        yolo: i32,
        swag: u8,
    }
    
    impl QuerySink2 {
        pub fn new(param1: i32, param2: u8) -> ComPtr<IWbemObjectSink> {
            let ptr = QuerySink2::create_raw(param1, param2);
            let ptr = ptr as *mut IWbemObjectSink;
            unsafe { ComPtr::from_raw(ptr) }
        }
    }
    

    Method names are converted from snake_case to PascalCase by com-impl.

    That's why for my QuerySink I used set_status and indicate which are mapped to SetStatus and Indicate. com-impl provides a #[com_name = "..."] attribute if you want to do it your way. The best way to understand the details is to look at the comments in https://github.com/Connicpu/com-impl/blob/master/derive-com-impl/src/lib.rs