Single-threaded Apartments

Using single-threaded apartments (apartment model) offers a message-based paradigm for dealing with multiple objects running concurrently. It allows you to write more efficient code by allowing a thread that is waiting for some time-consuming operation to allow another thread to be executed.

Each thread in a process that is intialized as apartment-model, and which retrieves and dispatches window messages, is a single-threaded apartment thread. Each of these threads live within its own apartment. Within an apartment, interface pointers can be passed without marshaling. Thus, all objects in one single-threaded apartment thread communicate directly. Interface pointers must be marshaled when passed between apartments.

A logical grouping of related objects that all execute on the same thread, and so must have synchronous execution could live on the same single-threaded apartment thread. An apartment-model object cannot, however, reside on more than one thread. Calls to objects in other processes must be made within the context of the owning process, so distributed COM switches threads for you automatically when you call on a proxy.

The inter-process and inter-thread models are similar. When it is necessary to pass an interface pointer to an object in another apartment (on another thread) within the same process, you use the same marshaling model that objects in different processes use to pass pointers across process boundaries. By getting a pointer to the standard marshaling object, you can marshal interface pointers across thread boundaries (between apartments) in the same way you do between processes.

Rules for single-threaded apartments are simple, but it is important to follow them carefully:

While multiple objects can live on a single thread, no apartment-model object can live on more than one thread.

Each thread of a client process or out-of-process server must call or call CoInitializeEx, and specify COINIT_APARTMENTTHREADED for the dwCoInit parameter. The main apartment is the thread that calls CoInitializeEx first. For information on in-process servers, refer to In-process Server Threading Issues.

All calls to an object must be made on its thread (within its apartment). It is forbidden to call an object directly from another thread; using objects in this free-threaded manner could cause problems for applications. The implication of this rule is that all pointers to objects must be marshaled when passed between apartments. COM provides two functions for this purpose. CoMarshalInterThreadInterfaceInStream, marshals an interface into a stream object that is returned to the caller, and CoGetInterfaceAndReleaseStream unmarshals an interface pointer from a stream object and releases it. These functions wrap calls to CoMarshalInterface and CoUnmarshalInterface functions, which require the use of the MSHCTX_INPROC flag.

In general, the marshaling is accomplished automatically by COM. For example, when passing an interface pointer as a parameter in a method call on a proxy to an object in another apartment, or when calling CoCreateInstance, COM does the marshaling automatically. However, in some special cases, where the application writer is passing interface pointers between apartments without using the normal COM mechanisms, the application writer must be aware that he is passing a pointer between apartments, and must handle the marshaling himself.

If one apartment (Apartment 1) in a process has an interface pointer and another apartment (Apartment 2) requires its use, Apartment 1 must call CoMarshalInterThreadInterfaceInStream to marshal the interface. The stream that is created by this function is thread-safe and must be stored in a variable that is accessible by Apartment 2. Apartment 2 must pass this stream to CoGetInterfaceAndReleaseStream to unmarshal the interface, and will get back a pointer to a proxy through which it can access the interface. The main apartment must remain alive until the client has completed all COM work (because some in-process objects are loaded in the main-apartment, as described in In-process Server Threading Issues. After one object has been passed between threads in this manner, it is very easy to pass interface pointers as parameters. That way distributed COM does the marshaling and thread switching for the application.

To handle calls from other processes and apartments within the same process, each single-threaded apartment must have a message loop. This means that the thread's work function must have a GetMessage/DispatchMessage loop. If other synchronization primitives are being used to communicate between threads, the Win32 function MsgWaitForMultipleObjects can be used to wait for both messages and thread synchronization events. The Win32 SDK documentation for this function has an example of this sort of combination loop.

COM creates a hidden window in each single-threaded apartment. A call to an object is received as a window message to this hidden window. When the object's apartment retrieves and dispatches the message, the hidden window will receive it. The window procedure will then call the corresponding interface method of the object.

When multiple clients call an object, the calls are queued in the message queue and the object will receive a call each time its apartment retrieves and dispatches messages. Because the calls are synchronized by COM and the calls are always delivered by the thread that belongs to the object's apartment, the object's interface implementations need not provide synchronization. Single-threaded apartments can implement IMessageFilter to permit them to cancel calls or receive windows messages when necessary.

The object can be re-entered if one of its interface method implementations retrieves and dispatches messages or makes an ORPC call to another thread, thereby causing another call to be delivered to the object (by the same apartment). OLE does not prevent re-entrancy on the same thread but it provides thread safety. This is identical to the way in which a window procedure can be re-entered if it retrieves and dispatches messages while processing a message.