top header
apartmentsterm

 

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

 
Apartments : The Basic Details
 
Apartments Recap

An apartment is a sub-division of a process containing a collection of contexts whose objects share common thread affinity. The rules of apartments are therefore consistent with the rules of context, and are defined on the previous page.

There may be (are likely to be) many objects in the same apartment. There are many threads in the single MTA, but only a single thread in each STA. There are no threads in the TNA.

Marshalling and interface remoting basics

For those of you who followed the optional marshalling and interface remoting trail, go down to here.

It is illegal to access an object directly outside of its context. Raw interface pointers are context-relative. In order to pass interface pointers from one context to another, we have to export from one context and import into the other.

Exporting and importing is essentially the practice of remoting an interface on an object to create a proxy in one context which talks to a stub in another in order to provide indirect access to the object across context boundaries. The proxy is a stand-in for the real object in the caller's context. This allows us to pass objects around by reference. Proxies and stubs communicate with a message-based protocol ORPC. COM has a flexible remoting architecture with extensibility points for providing your own interface remoting mechanisms, which allows both pass-by-value and pass-by-reference semantics. If you don't care to take advantage of this, COM provides you with a default remoting architecture that is called standard marshalling. We spent some time looking at standard marshalling on the optional marshalling and interface remoting trail because its implementation is tightly coupled to the notion of maintaining object identity when passing objects by reference across context boundaries.

This diagram shows how a client communicates with a proxy and how the proxy, in turn, communicates with the stub and eventually the real object.

apartments

To export an interface, CoMarshalInterface() is called on that interface. This is what a proxy would call if an interface was passed as an input parameter to a previously remoted interface. Using standard marshalling, exporting an interface from a context does two things:

  • It creates a context-neutral representation of the interface pointer that uniquely identifies the exporting context, object identity and interface. It may even identify the exporting machine and server process. This interface reference is said to be "marshalled" and is in a state suitable for transmission across context boundaries.
  • It creates an interface stub in the exporting object's context, which listens for incoming requests from a corresponding remote interface proxy. The interface stub represents the caller making a call on that interface but in the called object's context. It fields incoming requests from the proxy bound for the object on that interface and passes them on to it. It turns out, there is one interface stub for each exported interface on an object identity and a stub manager to coordinate the interface stubs for each exporting object identity. The stub manager for an object identity is created when the first interface stub for an object identity is created, i.e., when the first interface is exported on the object identity.

Once an interface has been exported, the context-neutral reference can be passed to another context in which it can be imported. It is just data so it can be passed in any number of ways. When an interface reference is passed as an interface method parameters, the data representing the interface is passed between proxy and stub just as it would be for any other parameter type.

To import an interface, CoUnmarshalInterface() is called on that interface. This is what a stub would call if an interface was passed as an input parameter to a previously remoted interface. Importing a context-neutral reference into a context re-hydrates it and creates a local object supporting the interface in question. This is called an interface proxy and it connects to the corresponding, listening interface stub. In fact, there is one interface proxy for each interface imported from a remote object identity and a proxy manager that coordinates their activity and acts as the context-local identity of the remote object identity.

Once connected, the ORPC request/response protocol is used to map from the context of the interface proxy to that of the interface stub and back again.

Click here to see an animation that shows the sequence of events that occurs when an interface reference is passed as a parameter to a method of an interface that is already remoted.

If we try to import an interface pointer into the same context that it was exported from, we get a direct pointer and not a proxy/stub pair! Also if we try and import a reference to an exported interface proxy, we do not get a proxy to a proxy - we just get an interface proxy back to the original interface stub!

Here is a simple diagram to illustrate.

wpeB.gif (7896 bytes)

So any reference that establishes a connection between an object and a caller outside of the object¡¯s context must be exported/imported. However, this is not as bad as it seems. If you already have a remote interface reference as a result of standard marshalling, you have a proxy/stub pair. That proxy/stub pair marshalls/unmarshalls all parameters passed between them. So any interface reference that is subsequently passed around as a parameter of that remoted interface is automatically marshalled/unmarshalled by the proxy and stub as a context-neutral reference. Standard marshalling perpetuates proxy/stub pairs silently.So if you only ever pass around interface references through interfaces, exporting/importing is done for you. Also, any COM API that gives out interface references will have exported/imported correctly also. For instance, CoCreateInstance() will create an object in another context and pass back a proxy if the object and caller are context-incompatible.  That leaves you only having to worry about explicitly exporting/importing interface references when you are handing them between contexts in a way that COM or the object itself wouldn't otherwise know anything about. We will look at how to do this later in this section.

How does a thread get assigned to a given type of apartment?

Freshly created threads must be assigned to an apartment before being able to use COM. Why? Object code executes on a thread. Threads offer thread affinity to objects. Threads better indicate which objects they offer thread affinity to. The choice for a thread is to be the only thread to assign to a new STA, or to join the group of threads in the MTA. They can do this by calling CoInitializeEx() with the COINIT_MULTITHREADED flag to assign to the MTA or the COINIT_APARTMENTTHREADED flag to create and assign to a new STA. A call to CoInitialize() is equivalent to a call to CoInitializeEx() passing the COINIT_APARTMENTTHREADED flag. That is, a call to CoInitialize() puts the calling thread into a newly created STA. Generally, CoInitialize(Ex) is called very early on in a thread's life.

Here are two examples:

void main() {
    HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED); // Assign to the MTA
    if (SUCCEEDED(hr)) {
        // use COM
        ...
    }
    else {
        // aaargh!
        ...
    }
}

int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR lpstrCmdLine, int nCmdShow ) {
    HRESULT hr = CoInitialize(NULL); // Create and assign to a new STA
    if (SUCCEEDED(hr)) {
        // use COM
        ...
    }
    else {
        // doh!
        ...
    }
}

Calling CoUninitialize() forces the thread to detach from the apartment it assigned itself to earlier and the thread cannot use COM again. Generally, a thread calls CoUninitialize() at the very end of its life. If an STA thread calls CoUninitialize(), any proxies being used by that thread are released and any objects that were created on that thread will not be available from other apartments any longer. If an MTA thread calls CoUninitialize() and the number of calls to CoUninitialize() in the MTA matches the number of calls made to CoInitialize() in the MTA, a similar thing happens. When any thread calls CoUninitialize(), there is a chance that any COM DLLs it was using could be ripped out of memory. The moral of the story is that you had better be careful when you call CoUninitialize().

How will an object get created in a given type of context?

The context that exports the first interface pointer to an object identity dictates the context in which that object 'lives'. When a caller attempts to create an object, the caller's context is already established. However, the context required by the object's class may be different. The class must therefore specify its context requirements declaratively. If it does this, the COM runtime can detect an incompatibility between caller and object, and ensure that the object is created in a compatible context and a proxy is marshalled back to the caller in its context, if necessary.

How does an object get created in a given type of apartment?

DLL Servers

A caller attempting to create an instance of a DLL-based COM class is doing so on a thread that has already assigned itself to an apartment. The COM class in question has specified its apartment affinity declaratively. If the COM runtime detects no mismatch, that is fine. The object lives in the callers apartment. In NT4 this means the caller gets a direct reference. If the COM runtime does detect a mismatch, it creates the object in a compatible apartment and exports the object from that apartment. This dictates the apartment the object 'lives' in. The caller imports the exported reference and gets a proxy.

The declarative attributes in question are discussed in following sections.

EXE Servers

An EXE-based class does not have to specify its apartment affinity declaratively.

Remember that when a caller creates and uses an out-of-process COM object, it is always talking to a proxy. An apartment is a sub-division of a process. A context is a sub-division of an apartment.

The EXE server gets to write the code that controls how a COM class is instantiated. It can, therefore, ensure that any of its objects are created in a compatible apartment. It does this by choosing to export the first reference to an object identity in an apartment that is compatible with the object . Normally this means unless any tricky class object code is written, the apartment that registered the class object is the one that creates an object identity and is the apartment that exports the first interface pointer to it.

How DLL-based COM classes specify apartment affinity declaratively

A DLL-based COM class must ensure that it marks itself with the correct declarative 'ThreadingModel' in the registry.

STA objects

Here is how you might mark your class to live in an STA:

registry apartmetns
registry apartmetns

MTA objects

Here is an example of marking your class to live in the MTA:

registry apartments registry apartments

Legacy STA objects

The absence of a threading model in the registry tells the COM runtime that you don't even understand threads, and thus marks your object as needing to live in the main STA along with all other legacy objects. Here is an example of a couple of ways that you could do this:

registry apartments registry apartments

registry apartments registry apartments

Context mismatch

So, the COM runtime (i.e., CoCreateInstance(), CoGetClassObject(), etc.) will be able to check to see whether there is a context mismatch between the caller and the class to instantiate. If there is a mismatch, the COM runtime can create the object in a compatible context then hand back a proxy to the caller. This may involve creating a new context of a compatible type, or using an existing context of a compatible type. If the context mismatch does not imply an apartment mismatch, a lightweight proxy is used which does not requires a thread switch or a marshall/unmarshall of the stack. If there is no mismatch, the COM runtime can create the object directly in the caller's context, and the caller gets direct access to the object.

Apartment mismatch

In particular, the COM runtime (i.e. CoCreateInstance(), CoGetClassObject(), etc.) can check to see whether there is an apartment mismatch between the caller and the class to instantiate. If there is a mismatch, the COM runtime can create the object in a compatible apartment then hand back a proxy to the caller. This may involve creating a new apartment of a compatible type, or using an existing apartment of a compatible type. This will be a heavyweight proxy that requires a thread switch and a marshall/unmarshall of the stack. If there is no mismatch, the COM runtime can create the object directly in the caller's apartment, and the caller gets direct access to the object.

Cross-apartment performance

When a caller uses an out-of-process object, it will always get a heavyweight proxy because the object certainly lives in another context from the caller (it's in another process!). When a caller uses an in-process object, it will get a heavyweight proxy if the object lives in another apartment. When a caller uses an in-process object, it will get a lightweight proxy if the object lives in another context within the same apartment.

In general, the performance overhead of a lightweight proxy will be proportional to the policy changes needed between contexts. It does not require a thread switch or marshalling/unmarshalling the stack.

A heavyweight proxy requires a thread switch and marshalling/unmarshalling the stack. In general, the performance overhead of a heavyweight proxy is proportional to the distance between apartments and the amount and type of data being transmitted. At DevelopMentor we have run performance tests to test out the cost of heavyweight proxies. It involves creating an object and accessing it via a custom interface method that has no parameters and does nothing. Here are the results:

Calls per second

same apartment (direct access)

3,000,000

between apartments in same process

5,000

between apartments in different processes on same machine

5,000

between apartments in different processes on different machines

2,000

So having indirect access to an object via a heavyweight proxy is quite costly. You might ask the question "I can see why I need a proxy between an MTA thread and an STA object. I can see why I need a proxy between an STA thread and an STA object. An STA object is not thread-safe and needs protecting. But why do I need to have a proxy between an STA thread and an MTA object if the MTA object is thread-safe anyway?"

The answer to this is as follows. When an interface is remoted there is a proxy/stub pair. The marshalling layer between them ensures that subsequent interface references passed in or out through the remoted interface are also remoted. Consider a caller in an STA that has access to an interface on an MTA object. Because the STA caller and the MTA object are in different apartment contexts, the caller calls though an interface proxy. Now, also consider that the caller has direct access to another STA object. An interface on the STA object is passed as a parameter to an interface on an MTA object, perhaps as a callback. The marshalling layer between the two apartment contexts would ensure that the interface on the STA callback object was remoted. So the MTA object's access to the STA object interface is through a proxy/stub pair and this is what we want.

If we did not have a proxy/stub pair between the STA caller and the interface on the MTA object, the interface on the STA callback object would not have been remoted. The MTA object would have had a direct pointer to the interface on the STA callback object. This is not what we want.

We HAVE TO go through the process of establishing a proxy/stub pair between any caller and object where there is any mismatch of context - Period. The only way to bypass the proxy/stub mechanism is to subvert it by custom marshalling of the object in order to make it context neutral.

ThreadingModel=Both objects

However, if you are an in-process class, but you don't want to pay the penalty of a heavyweight proxy/stub pair between you and a creator, you can mark your class as follows.

apartments apartments

This ensures that you will be loaded into the apartment of the creator. If you are created from an STA, you will be loaded into that STA. If you are created from the MTA, you will be loaded into the MTA.

The creator will get a lightweight proxy to the object if there is a context mismatch between it and the object. The creator will only get a direct reference to the object if there is no context mismatch. In NT4, the creator will always get a direct reference, since there is only one default context within each apartment.

It is only callers in the same context as the object that will get direct access to it. If a direct reference to the object is subsequently marshalled in the object's context and unmarshalled in a different one in the same apartment, there will be a lightweight proxy/stub pair. If a direct reference to the object is subsequently marshalled in the object's context and unmarshalled in a different one in a different apartment then there will be a heavyweight proxy/stub pair. If a lightweight proxy reference to the object is subsequently marshalled in the object's apartment and unmarshalled in a different one, there will be a heavyweight proxy/stub pair. Under NT4, if a direct reference to the object is subsequently marshalled in the object's context and unmarshalled in a different one, it will automatically be in a different apartment and there will be a heavyweight proxy/stub pair.

Remember that, even though MTA objects can only be called directly by threads in the MTA, it does not really matter to them what thread they are called from. They only have affinity to the MTA because they don't require affinity to any STA. They are certainly thread-safe. It is conceivable that an MTA object would spawn worker threads in the MTA to concurrently access itself. "Both" objects are really MTA objects in attitude that don't want to pay the penalty of a heavyweight proxy/stub pair between them and their creator.

Consider a scenario where a "Both" object is created in the STA. There is no heavyweight proxy/stub between the STA caller and the "Both" object.The STA caller then passes to the "Both" object a reference to another STA object in the same STA; no heavyweight proxy/stub between them either. The "Both" object holds on to the STA object as part of its state data. That STA object does have apartment affinity; it can only be touched by the thread in its STA. The "Both" object has to be careful about how it calls the STA object. It either has to guarantee to always call it from the STA object's apartment or call it from the MTA via a heavyweight proxy/stub.

A regular MTA object holding a reference to another STA object would not expect to do this. It would expect all other objects to have the same thread affinity characteristics as itself. That is, it would 'expect' a heavyweight proxy/stub between it and any STA object.

Remember from the section on standard marshalling that the proxy/stub classes in a marshalling DLL are always marked as "Both". This is to ensure that the proxy is always loaded into the importing apartment and the stub is loaded into the exporting apartment. Otherwise, we would recursively be loading proxies to proxies to proxies.....

So, in summary, the following table shows how the ThreadingModel flag in the registry affects whether we get a proxy to an in-proc object or a direct pointer.

apartments

Let's do a lab that enables an apartment-aware client to create threads, threads, enter them in apartments, and create objects with different threading models.

How do we pass object references across contexts explicitly?

We have already seen that if you already have access to an object in another context via a remoted interface (an interface proxy/stub pair), the marshalling layer will deal with remoting any other interface references that get passed as parameters through that remoted interface. We have also seen that if the COM runtime gives out interface references to us, they will have been remoted if they need to be. What about if we already have a direct, in-context interface reference to an object and want to pass it to another context?

This is the wrong way to do it.

// static initialization
IFoo *g_pFoo = NULL;
HANDLE g_hEvent = CreateEvent(0,FALSE,FALSE,0);

// thread A in context 1 (e.g. an STA apartment)
void SetFoo(IFoo *pFoo) {
(g_pFoo=pFoo)->AddRef();
SetEvent(g_hEvent);
}

// thread B in context 2 (e.g. the MTA apartment)
void GetFoo(IFoo **pFoo) {
WaitForSingleObject(g_hEvent,INFINITE);
*ppFoo = g_pFoo;
        g_pFoo=NULL;
}

Why? Because we have passed a raw interface pointer between contexts and this is illegal.

This is the right way to do it:

// static initialization
IStream *g_pStm = NULL;
HANDLE g_hEvent = CreateEvent(0,FALSE,FALSE,0);

// thread A in context 1 (e.g. an STA apartment)
void SetFoo(IFoo *pFoo) {
CoMarshalInterThreadInterfaceInStream(IID_IFoo,pFoo,&g_pStm);
SetEvent(g_hEvent);
}

// thread B in context 2 (e.g. the MTA apartment)
void GetFoo(IFoo **pFoo) {
WaitForSingleObject(g_hEvent,INFINITE);
CoGetInterfaceAndReleaseStream(&g_pStm,IID_IFoo,(void**)ppFoo);
        g_pStm=NULL;
}

CoMarshalInterThreadInterfaceInStream() is a wrapper function that creates a context-neutral stream and calls CoMarshalInterface to write an in-process, unmarshall once only interface reference into the stream. CoGetInterfaceAndReleaseStream() is a wrapper function that calls CoUnmarshalInterface to unmarshall the interface reference from the stream and then release it.

So, if we want to pass interface references between different contexts by a means other than a method on an already remoted interface, we have to explicitly export and import them.

This technique is not required when passing interface references around in the same context. However, it is important to duplicate such passed references with AddRef() in order to be able to use them safely.

Let's also do a lab that illustrates how to pass interface references across context boundaries correctly.

Next we will look at call dispatching in apartments.


Top of document