Tuesday, April 25, 2017

COM asynchronous call doesn't respect the message filter

Leave a Comment

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 */ 

0 Answers

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment