Registration-free COM-like interop and threading

2020-04-30 03:09发布

问题:

I’m working on a desktop app that has WPF GUI and unmanaged C++ backend. I’ve defined API the following way (C# version, truncated):

[Guid( "###" ), InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
interface iMyApp
{
    void aMethod();
}
[DllImport( "my.dll", PreserveSig = true )]
public extern static int create( string configPath, out iMyApp a );

In the backend I have

__interface __declspec( uuid( "###" ) ) iMyApp: public IUnknown
{
    HRESULT __stdcall aMethod();
}

And classic ATL-based implementation.

This works nice and fast, requires no COM registration, no type libraries, no MIDL compiler, etc.

The problem is, I can’t pass iMyApp instances across threads in C#, E_NOINTERFACE exception is raised.

My implementation is thread-neutral.

How to tell that to .NET so it stops marshalling my interfaces across threads, and just use the same IUnknown pointer on all threads? If it matters, I only need to support 4.5+

回答1:

As hinted by Hans Passant, the key here is CoCreateFreeThreadedMarshaler API.

Add a private variable CComPtr<IUnknown> m_ftm;

Initialize that marshaller:

HRESULT FinalConstruct()
{
    IUnknown* pUnk = GetUnknown();
    return CoCreateFreeThreadedMarshaler( pUnk, &m_ftm );
}

Process IID_IMarshal in QueryInterface, by adding the following line to the interface map:

COM_INTERFACE_ENTRY_AGGREGATE( IID_IMarshal, m_ftm )

After that, C# code will happily call those objects from any thread. Of course you'll have to handle synchronization yourself, CComAutoCriticalSection / CComCritSecLock often help.

Update: MS broke my code in VS 2017, updated the answer. See edit history for VS2015 version.