Interface and Implementation Revisited

Some developers make extensive use of multithreaded programming techniques and are able to write amazingly sophisticated software using the thread synchronization primitives available from the operating system. Other developers are more concerned with solving domain-specific problems and cannot be bothered with the nuisance of writing thread-safe or thread-hot code. Still other developers have special threading constraints due to the fact that many windowing systems (including Windows) have very strict rules regarding how threads and windowing primitives relate. Yet another class of developer may make extensive use of a legacy class library that is thread hostile and cannot tolerate any multithreaded access whatsoever. All four types of developers need to be able to use each other’s objects without having to rearchitect their threading strategy to accommodate all possible scenarios. To facilitate transparent usage of an object irrespective of its thread awareness, COM treats an object’s concurrency constraints as yet another implementation detail that the client has no business worrying about. To decouple the client from an object’s concurrency and reentrancy constraints, COM has a very formal abstraction that models how objects are related to both processes and threads. This abstraction is formally called an apartment.1 Apartments define a logical grouping of objects that share a common set of concurrency and reentrancy constraints. Every COM object belongs to exactly one apartment; however, one apartment can be shared by multiple objects. The apartment an object belongs to is implicitly part of an object’s identity.

An apartment is neither a process nor a thread; however, apartments share some of the properties of both. Every process that uses COM has one or more apartments; however, an apartment is contained in exactly one process. This means that every process that uses COM has at least one group of objects that share concurrency and reentrancy requirements; however, two objects that reside in the same process may belong to two different apartments and therefore could have different concurrency and reentrancy constraints. This principle allows libraries with wildly different thread awareness to interoperate peacefully in a single process.

A thread executes in exactly one apartment at a time. Before a thread can use COM, it must first enter an apartment. When a thread enters an apartment, COM stores information about the apartment in thread local storage (TLS), and this information remains associated with the thread until the thread exits the apartment. COM mandates that objects may be accessed only by threads executing in the apartment of the object. This means that if a thread is executing in the same process as an object, it may be prohibited from accessing the object even though the memory that the object occupies is fully visible and accessible. COM defines an HRESULT (RPC_E_WRONG_THREAD) that certain system-level objects will return when directly accessed from foreign apartments. It is legal for user-defined objects to return this HRESULT as well; however, few developers are willing to go to this length to ensure proper usage of their objects.

The Windows NT 4.0 release of COM defines two types of apartments: multithreaded apartments (MTAs) and singlethreaded apartments (STAs). Each process has at most one MTA; however, a process can contain multiple STAs. As their names imply, multiple threads can execute in an MTA concurrently, whereas only one thread can execute in an STA. More precisely, only one thread can ever execute in a given STA, which means not only that objects that reside in an STA will never be accessed concurrently but also that only one particular thread will ever execute the object’s methods. This thread affinity allows object implementors to safely store intermediate state in TLS between method calls, as well as to hold locks that have thread affinity (e.g., Win32-critical sections and mutexes) across method invocations.

These practices lead to disaster when used by MTA-based objects as there are no guarantees which thread will execute any given method invocation. The disadvantage of an STA is that it allows only one method call to execute concurrently, no matter how many objects belong to the apartment. In an MTA, threads can be dynamically allocated based on the current demand with no correlation with the number of objects in the apartment. To build concurrent server processes using only singlethreaded apartments, multiple apartments are necessary, which can cause excessive thread overhead if care is not taken. Also, the degree of concurrency in an STA-based server process cannot exceed the total number of objects in the process. If the server process contains only a small number of coarse-grained objects, then only a small number of threads can be utilized, even if each object lives in its own private STA.

A future release of COM will introduce a third type of apartment, the rentalthreaded apartment (RTA). Like an MTA, RTAs allow more than one thread to enter an apartment. Unlike an MTA, when the thread enters an RTA, it acquires an apartment-wide lock (hence, it rents the apartment) that keeps other threads from entering the apartment concurrently. This apartment-wide lock is released when the thread exits the RTA, allowing the next thread to enter. In this respect, an RTA is like an MTA except that all method calls are serialized. This makes RTAs much more suitable for classes that are not known to be thread safe. Although all calls in an STA are also serialized, RTA-based objects differ in that they do not have thread affinity; that is, arbitrary threads can execute inside the RTA, not just the initial thread that created the apartment. This lack of thread affinity makes RTA-based objects more flexible and efficient than STA-based objects, as any thread can conceivably call into the object simply by entering the object’s RTA. At the time of this writing, the details of how RTA apartments will be created and entered had not been finalized. Consult the SDK documentation for more details.

When a thread is first created by the operating system as a result of calling either CreateProcess or CreateThread, the newly created thread has no associated apartment.2 Prior to using COM, the new thread must first enter an apartment by calling one of the following three API functions:

HRESULT CoInitializeEx(void *pvReserved, DWORD dwFlags);
HRESULT CoInitialize(void *pvReserved);
HRESULT OleInitialize(void *pvReserved);

For all three API functions just listed, the first parameter is reserved and must be zero.

CoInitializeEx is the lowest level API function and allows the caller to specify which type of apartment to enter. To enter the process-wide MTA, the caller must use the COINIT_MULTITHREADED flag:

HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);

To enter a newly created STA, the caller must specify the COINIT_APARTMENTHREADED:

HRESULT hr = CoInitializeEx(0, COINIT_APARTMENTTHREADED);

Each thread in a process that calls CoInitializeEx with COINIT_MULTITHREADED executes in the same apartment. Each thread that calls CoInitializeEx with COINIT_APARTMENTTHREADED executes in a private apartment that no other threads can enter. CoInitialize is a legacy routine that simply calls CoInitializeEx using the COINIT_APARTMENTTHREADED flag. OleInitialize first calls CoInitialize and then initializes several subsystems used in OLE applications, such as OLE Drag and Drop and the OLE Clipboard. In general, it is preferable to call CoInitialize or CoInitializeEx if these higher level services will not be used.

Each of these three API functions can be called more than once per thread. The first call on each thread will return S_OK. Subsequent calls will simply reenter the same apartment and return S_FALSE. For each successful call to CoInitialize or CoInitializeEx, a call to CoUninitialize must be made from the same thread. For each successful call to OleInitialize, a call to OleUninitialize must be made from the same thread. These uninitialization routines have very simple signatures:

void CoUninitialize(void);
void OleUninitialize(void);

Failing to call these routines prior to thread or process termination may delay the reclamation of resources. Once a thread enters an apartment, it is illegal to change apartment types using CoInitializeEx. Attempts to do so will result in the HRESULT RPC_E_CHANGED_MODE. However, once a thread completely exits an apartment using CoUninitialize, it may enter another apartment by calling CoInitializeEx again.

1 Apartments are the more modern term for what the COM Specification originally referred to as an execution context.

2 This fact is subject to change under Windows NT 5.0. Consult the SDK documentation for more details.

© 1998 by Addison Wesley Longman, Inc. All rights reserved.