top header
apartmentsterm

 

home contents introduction com as a better C++ interfaces classes apartments labs lab setup quizzes
 
previous

 
Apartments : Call Dispatching

Here we will look in some detail at how calls are dispatched in STAs and in the MTA.

Threads as clients and servers : incoming and outgoing calls

An apartment can be thought of as an execution environment. Threads in the MTA or a thread in an STA can be performing one of two operations with regard to objects:

  • Making an outbound call, i.e., calling an object within or outside of the apartment, as the client of an object.
  • Receiving an inbound call, i.e., servicing a call from a thread outside of the apartment, as the server for an object.

If the apartment model is to be of any use, a thread making an outbound call must not be able to block an incoming call. In particular, callback scenarios, such as the one outlined in the following, must work when dispatching calls across apartments.

void ConnectAndMakeCall(IForward *pForward) {
    // instantiate an object in this thread
    IBackward *pBack = new CoBacward;
    // make blocking call to object in another thread
    pFoward->DoSomethingAndNotify(pBack);
}

HRESULT CoForward::DoSomethingAndNotify(IBackward *pBack) {
    long n;
    // call back into pBack
    pBack->GetNextValue(&n);
}

Causality

When a thread that is not currently executing an incoming method makes an outbound method call out of apartment, it is considered a top-level call. That is, it establishes a new call-chain. When a thread makes an outbound method call out of apartment while servicing an incoming method, it is considered a nested call. It is part of an existing call-chain. Top-level calls allocate a new causality ID (CID), which is a GUID that gets propagated from object to object as the call-chain extends. This is sometime called the logical thread ID. The CID is part of the ORPC packet header that describes every cross-apartment method call. Nested calls execute under the CID of the received packet.

The system uses this mechanism in an STA message filter (see later) to determine whether an incoming call to an object in an STA is a top-level call or a nested call. For incoming calls into the MTA, the CID is not really used by the system (or easily available to us!).

Let us look first at how calls are dispatched in the MTA and in an STA.

Call dispatching in the MTA

All COM executables will have an RPC thread cache for servicing incoming cross-apartment calls. Threads in the RPC thread cache run in the MTA. When such a thread is needed to dispatch a call to an object in the MTA, it simply calls the object directly. No attempt is made to serialize incoming calls in the MTA. Any free thread in the RPC cache can be used to dispatch calls and so MTA objects do not have thread affinity.

To see an animation that depicts how calls are dispatched in the MTA, click here.

If the client, on the left of the animation, is also a server, it will have an RPC thread cache. So while one MTA thread is blocked on an outgoing call, another thread in the RPC thread cache can service an incoming call; not a problem.

Call dispatching in an STA

The notion of a COM apartment only exists because there are certain thread-unaware object implementations that must not be compromised by concurrent multi-threaded access. Bear in mind that some library code used by object implementations may not be thread-safe and, worse still, some libraries abuse thread local storage and have thread affinity. Access to these kinds of objects must be serialized through a single thread; the same thread every time. In order to achieve this we have STAs.

Each thread that calls CoInitialize() or CoInitializeEx(COINIT_APARTMENTTHREADED) is its own single threaded apartment. All method calls bound for an STA object are serviced and dispatched by the single thread of the STA that 'owns' the object. Serialized access to objects in an STA is achieved as follows. A call on a proxy to an STA object results in a Windows message being posted to the thread message queue of the STA owning the object. This message represents the ORPC request. At a later date, the STA thread will pick up the message and dispatch it to the correct stub. The stub unmarshalls then calls the object directly.

So while an incoming method call into an STA is being dispatched, if the object doesn't yield to the message queue (GetMessage()/PeekMessage()/MessageBox() etc.), no other messages can be processed from the queue until the method call to the object has completed. This means during this time no other incoming method calls into the apartment will be serviced and neither will any user interface related messages. Essentially, an apartment-wide lock is acquired on each incoming method call to an object in the apartment so that the objects within the same STA are serialized with regard to each other and to the UI of that thread.

This does mean, of course, each STA thread must have a responsive GetMessage()/DispatchMessage() pump to operate correctly.

It turns out that the first STA thread in a process registers a window class with a channel-provided window procedure. Each STA creates an invisible window of this class to receive incoming ORPC request packets as a private window message. The window procedure calls the stub to decode the packet and call the object.

To see an animation that depicts how calls are dispatched between remote STAs, click here. Notice that the STA thread making the call out needs to stay responsive during the call, else there is no way that it can service incoming calls to objects in this STA (especially callbacks). Notice that the channel in its send/receive (that is running on the calling STA thread) gets one of the RPC threads in the MTA to do the blocking send/receive and goes into a message pump itself. This allows it to service other top-level calls or callbacks and certain user interface messages. The problem here is that we may get STA objects called re-entrantly when we are not expecting it and that may seriously compromise them. More on this later.

To see an animation that depicts how callbacks are serviced in a remote STA, click here. Notice how the STA thread making the initial call-out can still stay responsive to the callback.

Notice also how many threads are used in the remote case. Using four threads per method call is not ideal. In the local case the number of threads can be reduced. When communicating on the same machine there is a special RPC transport that uses window messages directly. A special blocking hook function is used to implement the client modal loop in the channel's send/receive to achieve COM callback semantics.

To see an animation that depicts how calls are dispatched between local STAs, click here.

Now we are going to do a lab that illustrates call dispatching.

This is the point at which you could stop working through the tutorial. You have now covered the fundamentals of COM.


Top of document