Invoke COM object in a "PostMessage" manner

284 views Asked by At

I have an STA thread in which I am currently executing some operation. Due to limitations of the environment (Office), there are certain things that I cannot do at this point in time. However, I can do these things immediately after the current message pump cycle is complete.

I would normally do this with my own message-only window handle to which I would post a message using PostMessage. However, given the environment and the architecture at hand, it is critical that the operation be queued alongside other COM object invocations coming from other threads/processes. Or in more exact terms, the post-invoke operations will need to be executed during a CoWaitForMultipleHandles call.

Does COM(+) provide a mechanism for invoking a method "later"? Other than:

  • Create a thread myself (or otherwise reuse a thread I have created myself). Make it an STA thread. Marshal my object interface to that thread. Use ICallFactory to create a call object for my async interface. Fire and forget from this secondary thread.
  • Create an MTA object strictly for the postback (the target COM server is still an STA object on the original STA thread). When instantiated, COM will create a thread for me. Call to that MTA object to schedule the postback (using the same ICallFactory method as before).
  • Dig out the COM dispatcher window handle and PostMessage to it.

The first two require a separate thread, which seems undesirable. The last is a hack.

1

There are 1 answers

5
noseratio On

Below are a few hacks to try. I haven't tried them myself in respect to CoWaitForMultipleHandles, so I'm not sure if any of them would work in your hosted Office environment:

  • Office provides an IMsoComponentManager service. You'd implement IMsoComponent (check this for some sample code). Hopefully, IMsoComponent::FDoIdle could be useful.

  • SetTimer can accept TIMERPROC lpTimerFunc. You don't have to create a window for that to work, you can use ATL thunks or a similar technique to create a C++ closure. I've done this with SetTimer and it works, but again I'm not sure if it works with CoWaitForMultipleHandles because the the timer callback normally gets invoked inside DispatchMessage. Besides, timer gets low priority in message dispatching, so it may be out of sequence.

  • WH_FOREGROUNDIDLE/ForegroundIdleProc might be interesting, using an ATL thunk. It should be called from inside CoWaitForMultipleHandles, too, when the latter is idle.

  • See if a custom COM message filter on your STA thread can be useful. Updated, you clarified it's an Office thread, so most likely it has its own COM message filter. Perhaps, you can override it for the scope of your call.

  • See if implementing IAdviseSink on your object can be useful. This interface is AFAIK the only exception to the COM rules in that its methods are called asynchronously when invoked via a COM proxy from another apartment. Perhaps, this would be best bet in solving your problem.

  • Updated, now that we know for sure you're running on a MSO STA thread, there's another hack. You can start a nested modal message loop, for the scope of your call. Then you can have control over each message pump cycle. This is a DoEvents-style hack I wouldn't normally recommend, but you're already in a hack zone. You'd at least need to do disable the Office UI (for the scope of the call) to limit the chance for re-entrancy. Then you'd initiate the nested message loop with IMsoComponentManager::FPushMessageLoop with msoloopDoEvents and expect IMsoComponent::FContinueMessageLoop to be called back upon each new iteration (pump).