I have an STA COM object that implements a custom interface. My custom interface has a custom proxy stub that was built from the code generated by the MIDL-compiler. I would like to be able to asynchronously make calls to the interface from other apartments. I'm finding that the synchronous interface calls respect the OLE message filter on the calling thread, but the asynchronous interface calls do not. This means that COM asynchronous calls cannot be used in a fire-and-forget manner if the calling apartment has a message filter that suggests retrying the call later.
Is this expected? Is there any way around this other than not using a message filter, not using fire-and-forget operations, or having a separate homegrown component just to manage fire-and-forget operations?
For the code below, MessageFilter is a simple, in-module implementation of IMessageFilter that routes calls to lambdas. If I do not use message filters, both the synchronous and asynchronous calls work fine. If I use the message filters shown below, the synchronous call works (after the main STA message filter stops returning SERVERCALL_RETRYLATER) but the asynchronous call immediately fails and never retries.
The main STA has a message filter that defers for some period of time.
// establish deferral time
chrono::time_point<chrono::system_clock> defer_until = ...;
// create message filter
auto message_filter = new MessageFilter;
message_filter->AddRef();
message_filter->handle_incoming_call
= [defer_until](DWORD, HTASK, DWORD, LPINTERFACEINFO)
{
return chrono::high_resolution_clock::now() >= defer_until
? SERVERCALL_ISHANDLED
: SERVERCALL_RETRYLATER;
};
// register message filter
CoRegisterMessageFilter(message_filter, nullptr);
Another STA sets up its own message filter to tell COM to retry.
// create message filter
auto message_filter = new MessageFilter;
message_filter->AddRef();
message_filter->retry_rejected_call
= [](HTASK, DWORD, DWORD)
{
return 0; // retry immediately
};
// register message filter
CoRegisterMessageFilter(message_filter, nullptr);
In that secondary STA, I get a proxy for the object interface from the main STA.
// get global interface table
IGlobalInterfaceTablePtr global_interface_table;
global_interface_table.CreateInstance(CLSID_StdGlobalInterfaceTable);
// get interface reference
IMyInterfacePtr object_interface;
global_interface_table->GetInterfaceFromGlobal(cookie, __uuidof(IMyInterface), reinterpret_cast<LPVOID*>(&object_interface)));
This works:
// execute synchronously
HRESULT hr = object_interface->SomeMethod();
/* final result, after the deferral period: hr == S_OK */
This does not work:
// get call factory
ICallFactoryPtr call_factory;
object_interface->QueryInterface(&call_factory);
// create async call
AsyncIMyInterfacePtr async_call;
call_factory->CreateCall(__uuidof(AsyncIMyInterface), nullptr, __uuidof(AsyncIMyInterface), reinterpret_cast<LPUNKNOWN*>(&async_call)));
// begin executing asynchronously
async_call->Begin_SomeMethod();
// end executing asynchronously
HRESULT hr = async_call->Finish_SomeMethod();
/* final result, immediate: hr == RPC_E_SERVERCALL_RETRYLATER */