Question

This is triggered by another question.

Specifically, I have a in process COM class, that is defined in the CLSID registry as having a ThreadingModel of Both.

Our process activates this object through CoCreateInstance (not CoCreateInstanceEx, if that even matters for an in-proc dll server)

Given a threading model of Bothand given th rules listed in the docs:

Threading model of server | Apartment server is run in
------------------------------------------------------
Both                      | Same apartment as client

and given what Hans writes in the other answer:

... Marshaling occurs when the client call needs to be made on a different thread. ... can happen when the ThreadingModel specified in the comClass element demands it. In other words, when the COM object was created on one thread but is called on another and the server is not thread-safe.

my tentative conclusion would be that such an object will never need implicit marshalling of calls to its interfaces, since the object will always live in the same apartment as its client.

Is that correct, even if the client process is running as STA?

Was it helpful?

Solution

Yes, there may be marshaling.

If the client of your COM class is running in an STA and you attempt to invoke your class from another apartment, it will have to marshal to the apartment that it was created in.

The COM terminology can be really confusing. When you refer to a 'client' in this case, you're really referring to a thread, not the entire application (as it would imply).

Both just means that the threading model of the server conforms to the client that instantiates it. That is, when you instantiate your class, it takes on the threading model of the thread it was created on. Since you're instantiating the server in an STA, your server will use STA, meaning it can only be invoked on the thread that created it; if another thread tries to invoke it, it will marshal to the thread it was created on.

OTHER TIPS

I can't help myself posting this, although it is not a direct answer to the question.

There's a brilliant MSKB article from the golden ages of COM: INFO: Descriptions and Workings of OLE Threading Models. Still there, and has all the relevant info. The point is, you should not worry about whether there is marshaling or not, if you follow the rules. Just register your object as ThreadingModel=Both, aggregate the Free-Threaded Marshaler with CoCreateFreeThreadedMarshaler, and be done. COM will do the marshaling if needed, in the best possible way. Depending on the client's apartment model, the client code may receive the direct pointer to your interface, if it follows the rules too.

Any "alien" interface that you may receive when a method of your interface gets called, will be valid in the scope of the call, because you stay on the same thread. If you don't need to store it, that's all that matters.

If however you do need to cache the "alien" interface, the right way of doing this would be to store it using CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream:

To store it:

  • Enter critical section;
  • call CoMarshalInterThreadInterfaceInStream and store the IStream pointer in a member field;
  • Leave critical section;

To retrieve it

  • Enter critical section;
  • call CoGetInterfaceAndReleaseStream to retrieve the interface
  • call CoMarshalInterThreadInterfaceInStream and store it again as IStream for any future use
  • Leave critical section;
  • Use the interface in the scope of the current call

To release it:

  • When you no longer need keeping it, just release the stored IStream (inside the critical section).

If the "alien" object is free-threaded too, and the things are happening inside the same process, you will likely be dealing with a direct interface pointer after CoGetInterfaceAndReleaseStream. However, you should not make any assumptions, and you really don't need to know if the object your dealing with is the original object or a COM marshaller proxy.

This can be slightly optimized by using CoMarshalInterface w/ MSHLFLAGS_TABLESTRONG / CoUnmarshalInterface / IStream::Seek(0, 0) / CoReleaseMarshalData instead of CoGetInterfaceAndReleaseStream/CoGetInterfaceAndReleaseStream, to unmarshal the same interface as many times as needed without releasing the stream.

More complex (and possibly more efficient) caching scenarios are possible, involving Thread Local Storage. However, I believe that would be an overkill. I did not do any timing, but I think the overhead of CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStreamis really low.

That said, if you need to maintain a state that stores any resources or objects which may require thread affinity, other than aforementioned COM interfaces, you should not mark your object as ThreadingModel=Both or aggregate the FTM.

Yes, marshalling is still possible. A couple of examples:

  1. the object is instantiated from an MTA thread and so placed into an MTA apartment and then its pointer is passed into any STA thread and that STA thread calls methods of the object. In this case an STA thread can only access the object via marshalling.

  2. the object is instantiated from an STA thread and so placed into an STA apartment belonging to that thread and then its pointer is passed into another STA thread or an MTA thread. In both cases those threads can only access the object via marshalling.

In fact you can expect no marshalling only in the following two cases:

  1. the object is instantiated from an MTA thread and then only accessed by MTA threads - both the one that instantiated the object and all other MTA threads of the same process.
  2. the object is instantiated from an STA thread and then only accessed by that very thread

and in all other cases marshalling will kick in.

ThreadingModel = Both simply means that the COM server author can give a guarantee that his code is thread-safe but cannot give the same guarantee that other code he didn't write will be called in a thread-safe way. The most common case of getting such foreign code to execute is through callbacks, connection points being the most common example (usually called "events" in client runtimes).

So if the server instance was created in an STA then the client programmer is going to expect the events to run on that same thread. Even if a server method that fires such an event was called from another thread. That requires that call to be marshaled.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top