Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I ensure no re-entrant access to my main-STA COM server (C++)?

Tags:

c++

com

winapi

OK, I suspect I'm going to have difficulty even putting this in words since my understanding of COM and apartments isn't really up to the job ;-)

I have a COM in-process server/component (C++) that wraps some legacy code. Due to the limitations of this legacy code I need to ensure that the methods of the COM component are:

  1. only called on a single thread.
  2. this is always the same thread for all instances of the server.
  3. (which I only realised later) no re-entrant calls.

The first two I achieved by registering the server with ThreadingModel="".

The 3rd is a problem I was suprised to even encounter.

The server is being used by a multi-threaded client which I have no control over. It is creating multiple instances of the server/component on different threads and calling their DoSomething() method.

This is leading to a selection of hanging and crashing behaviour and I have seen stack traces containing two calls to DoSomething() both on the main-STA thread, but for different instances of the server.

I initially didn't even think this was possible, but I now have a partial understanding and I need to know if/how it can be prevented.

My reading suggests that I may need to use IMessageFilter in some fashion, but I'm not sure if this is something that can be done on the server side, or needs to be done by the client.

Can anybody help?

Please note I am looking to see if there are any answers at the COM level, rather than looking for suggestions about changing the way that the server code interacts with the legacy code (e.g. by running the legacy code in its own thread and implementing my own (non-COM) marshalling of the calls from all instances of the server onto that thread).

like image 223
Tom Williams Avatar asked Feb 16 '10 12:02

Tom Williams


3 Answers

COM marshals calls to your server through the message loop. You can get into re-entrancy hell with the COM modal loop. That loop takes care of outgoing calls that need to be marshaled back to the client thread. Like events. Or calls your server makes to another server that's in a different STA apartment. While the modal loop pumps messages, waiting for the outgoing call to be completed, it can pick up incoming calls, just like the regular message loop.

Technically, re-entrancy can also happen when your server is trying to keep a UI alive by pumping messages itself.

Yes, this can be very hard to deal with. I'm not aware of any magic fix for this, and I've been looking hard for quite a while. I also looked at IMessageFilter and concluded it was useless to solve this problem.

like image 76
Hans Passant Avatar answered Nov 10 '22 13:11

Hans Passant


Preventing re-entrancy altogether in a generic fashion could prove almost impossible to implement due to the way COM marshalling is implemented, as nobugz have described already. I guess it is possible to detect it either manually or by using custom "proxies" (check e.g. The Guts of the Universal Delegator by Keith Brown).

Just an idea though: If your COM object should always run in an STA, and only gets called from clients running in other apartments (i.e. MTA), maybe you could read up on custom marshalling (warning: not for the faint of heart). As your legacy code does not seem to have any requirements to run in the main STA, or more specifically interact with GUI components this might prove an alternative.

like image 27
rjnilsson Avatar answered Nov 10 '22 14:11

rjnilsson


As nobugz points out, this is a problem due to the message loop.

I'd probably look at solving this inside the COM object. It depends how complex your calls are but you might consider having a work queue inside your COM object which is processed by a single thread that is under your control. You then manually marshal your input parameters into a data block that you post to the queue, your COM call then blocks on an event in your 'per call' custom queueing data structure. When the call is processed on the one thread that is processing your work queue you extract the parameters, make the call and then package the result into the custom data structure and set the event... Your com call can now unpack things and return.

Of course the devil is in the detail. I'd personally use an IOCP to build the custom queue but that's just an implementation detail.

Yes you're doing more work but given your requirements (one thread to make all the calls and no reentrancy) it's probably the most reliable way to do it. You can then, of course, drop the STA requirements for the actual COM object itself as you've made a threading aware object that can run in any appartment...

like image 26
Len Holgate Avatar answered Nov 10 '22 14:11

Len Holgate