4

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 */
Michael Gunter
  • 12,528
  • 1
  • 24
  • 58
  • when you do synchronous call, *COM* run internal message loop while wait for responce, and from this loop and call methods of *IMessageFilter*. if you do asynchronous call - *COM* return immediately - no any internal loops and as result nobody (except you yourself) will be not call *IMessageFilter*. from another side with asynchronous model you hold control all time. you yourself run own message loop and can do any what you want. you not need *IMessageFilter* in this case – RbMm Apr 19 '17 at 14:01
  • Agreed. That's exactly the behavior I'm seeing. Upon further thought, it makes sense. Since this is an STA, the only opportunity to run the retry logic here is in a message pump (or CoWait) and if you begin multiple async operations, they would only try once before a message loop allows them to retry again, which might not be timely. – Michael Gunter Apr 19 '17 at 15:18
  • Running my own loop falls into the category of "homegrown component". Is there anything out-of-the-box that would facilitate this? I can't think of a good way to make a framework-level component to manage this, since the retry loop would be hardcoded with the Begin_XXX calls. The message filter "taps into the call chain," so to speak. Is there any other way of doing this without, say, building my own proxy class? – Michael Gunter Apr 19 '17 at 15:22
  • in [Remarks for IMessageFilter](https://msdn.microsoft.com/en-us/library/windows/desktop/ms693740(v=vs.85).aspx) this stated - *COM enters a modal loop while waiting for the reply* and only from this modal loop *IMessageFilter* called. asynchronous really must be event based - you do begin call and not waiting for it end continue do own tasks (or simply run own message loop) when call finished - you got message (from loop) about this. and handle result. so you simply process messages from loop as usual. – RbMm Apr 19 '17 at 15:31
  • If I just run a plain message loop, it doesn't work. I get exactly one message after the Begin_XXX call - an 0x407 for the COM window that manages calls/callbacks to the STA. I assume that's the message that contains or references the completion (failure) info. The COM STA window proc doesn't retry for me if I dispatch the message. If I want to create a generic message pump (or some event-based solution) that can tap into the message filter, I need to be able to decode this completion info and, if possible, programmatically find enough information to retry the call. Time to find the specs... – Michael Gunter Apr 19 '17 at 16:50

0 Answers0