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.
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 implementIMsoComponent
(check this for some sample code). Hopefully,IMsoComponent::FDoIdle
could be useful.SetTimer
can acceptTIMERPROC 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 withSetTimer
and it works, but again I'm not sure if it works withCoWaitForMultipleHandles
because the the timer callback normally gets invoked insideDispatchMessage
. 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 insideCoWaitForMultipleHandles
, 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 withIMsoComponentManager::FPushMessageLoop
withmsoloopDoEvents
and expectIMsoComponent::FContinueMessageLoop
to be called back upon each new iteration (pump).