As described in Chapter 2, COM defines an abstraction, called an apartment, for managing concurrent access to components. An apartment is a grouping of objects that share the same threading requirements. A single-threaded apartment (STA) can house one and only one thread. The multithreaded apartment (MTA) can house any number of threads. Every thread that wants to gain direct access to a given COM object must reside in an apartment—the same apartment, in fact, in which the COM object resides. Before accessing the object, a thread must call CoInitialize or CoInitializeEx, whereby it specifies which type of apartment it wants to enter. When a thread creates an instance of a COM object, that object might or might not reside in the same apartment as the creating thread, depending on the apartment preference specified by the object itself. Because a thread can directly access only the objects that reside in its own apartment, communication with objects residing in other apartments can occur only by means of a proxy. A proxy is an object that resides in the apartment of the calling thread that behaves as if it were the object itself. The proxy forwards method calls from one apartment to another—a technique known as method remoting—ensuring that all calls are made by threads running in the object's apartment. On the server side, a special interface stub object that runs in the address space of the callee unmarshals the parameters for that interface and makes the requested method call. The proxy and stub typically communicate with one another using Object Remote Procedure Call (ORPC) requests.
The beauty of the apartment model is that a client can remain blissfully ignorant about the threading limitations of the COM object, treating those requirements as essentially an implementation detail.
This information on apartments and threading is specific to Windows NT 4.0. The apartment story changes with Windows 2000.
Method remoting not only expedites communication between apartments, it also facilitates communication between objects running in different processes and on different machines via a technique known as marshaling.1 The three marshaling techniques are described in Table 4-4.
Table 4-4. Marshaling Techniques.
|Custom||Object implements the IMarshal interface and handles its own cross-apartment communication. This technique is used only in the rare cases in which the COM-provided marshaling techniques don't suffice.|
|Type library||Uses OLEAUT32.DLL to marshal the interface. The type library marshaler provides marshaling only for the Automation data types. The component specifies type library marshaling using the [oleautomation] or [dual] Interface Definition Language (IDL) attributes. Type library marshaling supports marshaling between 16-bit and 32-bit applications.|
|Standard||COM looks in the Registry to find the correct proxy/stub DLL for marshaling the interface. The object must define custom data types in IDL and provide registered proxy/stub objects for each marshaled interface. More than one proxy/stub object can reside in the same server DLL.|
As you can see from Table 4-4, type library marshaling is used with Automation-compliant interfaces and uses the built-in proxy/stub objects the system provides. Type library marshaling is an appropriate choice for dual interfaces or interfaces with methods that use only the primitive VARIANT-compatible Automation data types. Standard marshaling is much more flexible because it allows you to create sophisticated data structures in IDL, but it requires you to build and register in-process proxy/stub objects.
It's not a strict requirement that interfaces marked with the [dual] keyword use the type library marshaler. In theory, there's nothing to prevent you from registering a custom proxy/stub DLL for dual interfaces, but that is rarely done in practice.
Along with the workspace and project files, the ATL COM AppWizard generates a makefile that you can use to create an in-process DLL containing the proxy and stub objects required to marshal the interfaces exposed by your COM server by means of the standard marshaler. This makefile, named <projname>PS.MK, is placed in the same folder as the wizard-generated workspace. If you're not planning to use the standard marshaler, or if you know that your component will never be accessed by a thread residing in a separate apartment, you might never need to compile the proxy/stub DLL. Otherwise, here's how to register the proxy:
nmake -f TipServerps.mk
If your component rolls its own IMarshal interface or if your component exposes dual interfaces, you can simply leave the proxy/stub makefile alone. Otherwise, you should always build, redistribute, and register the proxy/stub DLL. If an interface marshaled by means of the standard marshaler resides in an EXE server, the proxy/stub is always required. Even if the interface resides in a DLL, chances are good that a client will try to access the interface from a background thread. Redistribute and register the proxy/stub!
The proxy object must reside in an in-process server DLL rather than an EXE server because it must be loaded into the same apartment—and hence the same process—as the client thread. The same principle applies to the relationship between the stub and the server thread. When cross-apartment communication is required on a single machine that hosts both the client application and the server component, the proxy/stub objects for each marshaled interface must be registered on that machine. When the communication crosses machine boundaries, the proxy/stub DLL must be registered on both the client and server machines.
If you're developing an in-process server, the ATL COM AppWizard provides an option that allows you to house the proxy/stub objects within the same DLL as the server itself, thereby reducing the number of files that must be redistributed with your component. (Refer to Figure 4-4.) Choosing the Allow Merging Of Proxy/Stub Code wizard option still creates a separate proxy/stub makefile, so it doesn't preclude you from housing the proxy/stub objects in a separate DLL. But selecting the merge option creates extra code that will merge the proxies into the component server if you define the _MERGE_PROXYSTUB preprocessor symbol.
If you're developing an in-process server and you're fairly certain that your component will always reside on the same machine as its clients, merging the proxy/stub objects into the server is probably a good idea. Merging them reduces the number of modules you must compile, register, and redistribute, and the footprint will be smaller than the combined footprint of separate server and proxy/stub DLLs. If you're planning to deploy your in-process server on a remote machine under the auspices of a surrogate, however, merging the two DLLs might not be such a good idea. Because you must register the proxy/stub object on the client machine, merging will mean that the implementation code for your component must be installed on the client machine even though the code runs on a separate machine.