KEMBAR78
Add marshaling support for COM implementations by kennykerr · Pull Request #3531 · microsoft/windows-rs · GitHub
Skip to content

Conversation

@kennykerr
Copy link
Collaborator

@kennykerr kennykerr commented Mar 6, 2025

In many cases, an explicit IMarshal interface implementation is not required when implementations support IAgileObject. But there are cases when this is insufficient and COM demands an implementation of IMarshal.

This update adds a universal implementation of IMarshal for all COM objects produced by windows-rs including the implement macro, delegates, and weak references. This implementation is based on the system's free-threaded marshaler and is implemented identically to the way I originally implemented it for C++/WinRT which has stood the test of time. Specifically, IMarshal is implemented as a tear-off to avoid any overhead in the common case where marshaling is not required.

Here's a simple client-server example to illustrate what this enables. Here's the server that uses CoRegisterClassObject to register and host a class factory:

use windows::{Foundation::*, Win32::System::Com::*, core::*};

#[implement(IClassFactory)]
struct Factory;

impl IClassFactory_Impl for Factory_Impl {
    fn CreateInstance(
        &self,
        _: Ref<IUnknown>,
        iid: *const GUID,
        interface: *mut *mut core::ffi::c_void,
    ) -> Result<()> {
        unsafe {
            let unknown: IUnknown = Stringable.into();
            unknown.query(iid, interface).ok()
        }
    }

    fn LockServer(&self, _: BOOL) -> Result<()> {
        Ok(())
    }
}

#[implement(IStringable)]
struct Stringable;

impl IStringable_Impl for Stringable_Impl {
    fn ToString(&self) -> Result<HSTRING> {
        Ok("Stringable".into())
    }
}

const CLSID_SAMPLE: GUID = GUID::from_u128(0x58d39529_4975_4440_b7a6_6d71bfb59559);

fn main() -> Result<()> {
    unsafe {
        CoIncrementMTAUsage()?;
        let factory: IClassFactory = Factory.into();

        CoRegisterClassObject(
            &CLSID_SAMPLE,
            &factory,
            CLSCTX_LOCAL_SERVER,
            REGCLS_AGILE | REGCLS_MULTIPLEUSE,
        )?;

        println!("Press key to end server...");
        getchar();
        Ok(())
    }
}

unsafe extern "C" {
    pub fn getchar() -> i32;
}

And here's a client that uses CoCreateInstance to remotely connect to the server:

use windows::{Foundation::*, Win32::System::Com::*, core::*};

const CLSID_SAMPLE: GUID = GUID::from_u128(0x58d39529_4975_4440_b7a6_6d71bfb59559);

fn main() -> Result<()> {
    unsafe {
        CoIncrementMTAUsage()?;

        let stringable: IStringable = CoCreateInstance(&CLSID_SAMPLE, None, CLSCTX_LOCAL_SERVER)?;
        println!("client: {}", stringable.ToString()?);

        Ok(())
    }
}

Fixes: #753

@kennykerr kennykerr changed the title Add optional marshaling support for COM implementations Add marshaling support for COM implementations Mar 6, 2025
@kennykerr kennykerr merged commit 4523c9d into master Mar 7, 2025
34 checks passed
@kennykerr kennykerr deleted the marshaler2 branch March 7, 2025 16:19
@Progdrasil
Copy link

Hi @kennykerr, thank you for this work! When do you think this will be available in a published windows-rs version?

@kennykerr
Copy link
Collaborator Author

You're very welcome. Releases are on demand only, so just test against the master branch to make sure everything works as expected. Then if you are in need of a published release and I'll be happy to set that in motion.

This was referenced Mar 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implementations must support IMarshal

2 participants