|< BACK  NEXT >|
All along in this chapter I have mentioned several times that a cross-apartment access to an STA or MTA object requires a thread switch. It is interesting to learn how COM implements an apartment and why a cross-apartment call results in a thread switch. As most objects elect to use standard marshaling for cross-apartment (and cross-context) access, I will focus on the same.
We know from Chapters 2 and 5 that, if the client and the objects are in two different apartments, the client gets a proxy to the object. More specifically, the client gets a proxy manager that loads an interface proxy for each interface queried on the object. When a method call is made on the interface proxy, the interface proxy marshals the parameter information into an ORPC message and sends it over the ORPC channel to the interface stub. The interface stub receives the ORPC message, unmarshals it, and invokes the appropriate interface method on the object.
While communicating over the ORPC channel, COM uses the most efficient transport protocol available based on the type of importing (client s) and exporting (object s) apartment.
If the importing apartment is on a different host, COM uses RPC as the transport protocol. An RPC thread cache is started in the exporting apartment s process. This thread cache waits for incoming connection requests. When a request comes in, the thread cache will dispatch a thread to service the request and continue to wait for additional requests. When the thread is done servicing the request, it will return back to the thread cache.
If the importing apartment is on the same host but in a different process, COM uses lightweight RPC (LRPC) as the transport protocol. LRPC is a Microsoft proprietary variant of RPC which has the exact same calling syntax as the DEC RPC run time (thus, the seamless interchangeability), but is optimized to reduce data copying and eliminate access to the networking code altogether.
If the importing apartment is in the same process, COM can bypass the RPC thread cache and use the caller s thread directly to service the request.
In summary, the thread that services the incoming request could be one from the RPC thread cache or could be the client s thread itself. The thread will dispatch the call to the object s apartment, wait for a response, and return the response back to the client.
Now let s see how the calls are dispatched to the actual apartment.
If the incoming call originates from a different host, we know that the call has to be dispatched using a thread from the RPC thread cache. However, an STA has its own thread to work with. No other threads can enter an existing STA.
We therefore need a way to communicate between the RPC thread and the STA thread. Here is what COM does.
To enter the STA and dispatch a call to the STA s thread, the RPC thread packs the ORPC message into a Windows message and posts it (using PostMessage) to the STA thread s MSG queue.
From our earlier discussion, we know that in order to process a message from a message queue we need a window handle, a WndProc, and a message pump.
COM takes care of providing the window handle and the WndProc. It creates an invisible window for each thread that enters an STA. The WndProc of this window receives the message posted by the RPC thread and passes the embedded ORPC message to the stub. The interface stub processes the ORPC message in the context of the STA thread.
Developers have to take care of implementing the message pump. Each STA thread should implement a message pump.
Figure 6.4 illustrates the call dispatching sequence in an STA.
If the incoming call originates from the same host but from a different process, a special transport is used that bypasses the RPC thread cache and calls PostMessage from the thread of the caller. This is because Windows lets you post a message from a different process on the local machine, but not on a remote machine.
If the incoming call originates from the same process but from a different apartment, the ORPC channel can directly use the client thread to post a message.
Obviously, if the call originates from the same STA apartment, the call will be processed directly, bypassing the message queue. There is no need to switch the thread.
From a programming perspective, two important points are to be noted:
A cross-apartment call into an STA always results in a thread switch.
An STA requires a message pump to process incoming requests.
The second point deserves a little more attention.
The message pump in the STA is not just used for processing incoming ORPC requests, but can also be used for any non-COM-related window messages (recall that an STA thread is an ideal candidate for UI processing). In either case, if an interface method call takes a considerable time to execute, no incoming ORPC requests would be processed and the UI would appear to be frozen. Therefore, any interface method in an STA should not do a blocking wait (using WaitForSingleObject, for example). Moreover, if the method is expected to take a considerable time to execute, then it should implement some variant of the message pump in the method implementation code. For example, at some strategic points in the code, the method could check if a new message has arrived in the queue and dispatch that message.
The same holds true when an STA thread makes either a cross-apartment method call into an MTA or into a different process method call. Recall that when such a method call is made, the proxy sends an ORPC message and waits for an ORPC response. The call is blocked until a response is received. A blocking call such as this would prevent incoming messages on the MSG queue to be serviced.
To be able to process the incoming messages, the ORPC channel uses a different thread to wait for the ORPC response (the thread gets created when the caller first receives the proxy). As the caller s thread cannot be released until a response is received, the channel puts the caller s thread in an internal message pump. This internal message pump serves:
the incoming ORPC requests (as Windows messages)
normal non-COM-related Windows messages
the ORPC response message from the thread waiting for the ORPC response
Having an internal message pump in the ORPC channel also solves the problem of potential deadlock when an STA thread makes a cross-apartment method call, and the method turns around and tries to enter the STA thread (this deadlock problem is similar to the one we described earlier for activities). While the cross-apartment method call is still waiting for an ORPC response, the nested call can now reenter the STA.
After knowing how STA calls are dispatched, learning how a call is dispatched to an MTA is a piece of cake.
If the incoming call originates from a different host or from a different process on the same host, a thread from the RPC thread cache services the request. It enters the object s apartment and passes the ORPC message to the stub.
If the call originates from an STA within the same process, it is equivalent to the case we covered earlier where an STA thread makes an outgoing call into an MTA. One of the threads from the RPC cache enters the MTA, passes the ORPC message to the stub, receives the ORPC response, and posts it as a Windows message back to the STA thread.
If the call originates from an MTA within the same process, the ORPC channel uses the caller s thread to enter the MTA and process the request. No thread switch is required.
Dispatching calls to a TNA is even simpler.
If the call originates from a different host or from a different process on the local host, the dispatching mechanism is similar to that of an MTA. A thread from the RPC thread cache enters the TNA and passes the ORPC message to the interface stub.
If the call originates from a different apartment within the same process, the ORPC channel uses the caller s thread to enter the TNA. There is no thread switch involved. The only performance hit is that the lightweight interceptor has to set up the proper context before entering the TNA and then reset it upon exiting.
If the object is configured to run under a rental apartment, all the other ORPC requests are blocked once a thread enters a TNA.
When an outgoing call is made from the TNA, it is done on either an STA or MTA thread, depending upon where the call originated. The same call dispatching rules apply as discussed earlier. The fact that the call was routed through a TNA is of no consequence. For example, if the call originated from an MTA, through a TNA, to an MTA (in the same process), the ORPC channel will use the original caller s thread to execute the method.
|< BACK  NEXT >|