George Shepard and Scot Wingo
Q. We’re thinking of using the Active Template Library (ATL) at our company as a framework for implementing our COM classes. Unfortunately, the documentation is somewhat sketchy concerning all the options available through the Visual C++® ATL COM AppWizard and the ATL Object Wizard. Exactly what happens when I check the various options? What types of MFC support does ATL include? What’s the difference between a regular COM EXE server and a service? What does it mean to merge the proxy/stub code with my DLL? Inquiring minds want to know.
A. You’ve asked a really good question. For excellent coverage of ATL’s architecture, be sure to see “The Active Template Library Makes Building Compact COM Objects a Joy,” by Don Box (MSJ, June 1997). When it comes to using the ATL COM AppWizard and the ATL Object Wizard, you have a lot of options that are pretty much glossed over in the documentation that comes with Visual C++. Most people are baffled at first by the dazzling array of different options available when creating ATL-based COM classes through these wizards. Let’s start by examining what each of the different ATL COM AppWizard options does and then we’ll see what the various options within the ATL Object Wizard do.
Developing a COM server involves myriad issues, including creating class objects for your COM classes, registering your COM classes in the registry, giving external clients a way to get to your class objects, and taking care of the server lifetime considerations. If you’re creating a COM DLL, you need to write several functions that the COM runtime expects to see, including DllGetClassObject, DllRegisterServer, and DllUnregisterServer. You need to provide your COM class with a reference count so it can track the number of outstanding objects it’s still serving. You’ll also need to export DllCanUnloadNow. If you’re creating a COM-based EXE server, you need to register your class objects at runtime using CoRegisterClassObject, and you need to check the command line for /REGSERVER or /UNREGSERVER. An EXE server also needs to maintain a reference count and post a WM_QUIT message to itself when the object reference count drops to zero.
All of this requires the developer to pump out a lot of code. As it turns out, much of it is boilerplate code. ATL takes a load off developers by implementing most of the boilerplate code necessary for developing simple, straightforward COM classes.
ATL also serves as a lightweight alternative to MFC for writing ActiveX™ controls (which are just COM classes with a bunch of interfaces hanging off them). While MFC continues to be the most convenient way to write ActiveX controls using C++ (Visual Basic® is probably the most convenient means), MFC is not without faults. The most difficult-to-swallow aspect of MFC is its runtime DLL, which must accompany an ActiveX control wherever it goes. Even though it doesn’t need all the functionality available in the DLL, the control still has to carry around the entire enchilada just to work.
Because ATL is a template library, the critical chunks of code necessary for COM classes to work are brought in via template inheritance rather than normal C++ implementation inheritance. Templates actually copy bits of code into your class and may be customized using template parameters. As a result, code compiled using templates tends to be a bit larger, but more self-contained than normal C++ code.
Creating a COM-based server in ATL is a simple matter of exercising the ATL COM AppWizard. Just select New from the File menu and choose ATL COM AppWizard (see Figure 1). Once confronted with this dialog, you’ve got several options from which to choose. For starters, you can select from a COM-based DLL, a COM-based EXE, and a COM-based service. Each of these options produces the correct code for the given context. Let’s start by taking a look at what you get with an COM-based DLL.
Figure 1: The ATL CO AppWizard
The default mode for the ATL COM AppWizard is to create a COM-based DLL. The COM-specific aspects of a DLL include four well-known entry points: DllGetClassObject so that the COM runtime can retrieve a class’s class object or class factory; DllCanUnloadNow so the COM runtime knows whether or not it can unload the DLL; DllRegisterServer to insert appropriate entries into the registry; and DllUnregisterServer to remove a COM class’s entries from the registry.
Selecting a COM-based DLL from the ATL COM AppWizard yields the following files for building a COM-based DLL:
Compiling the project yields three more files:
In addition to the source code files listed above, you also get project files for a proxy/stub DLL based on the contents of project name.idl. This includes three more files named project nameps.def, project nameps.mk, and dlldata.c. The latter is produced if project name.idl has interfaces defined in it.
As far as clients are concerned, an ATL COM-based DLL is just a regular COM DLL. It’s just that many of the implementation details are handled by ATL itself.
One of the most interesting aspects of ATL is that while ATL is meant to be a lightweight alternative to MFC for creating ActiveX controls, you can include a major portion of MFC in your ATL COM-based DLL if you want. Just mark the Support MFC checkbox before building your DLL. Clicking on Support MFC within the ATL COM AppWizard simply inserts these additional lines into stdafx.h:
#include <afxwin.h>
#include <afxdisp.h>
// STDAFX.H includes MFC by including these
// two files if you mark the "Support MFC"
// check box
The AppWizard also links the import library for the MFC runtime DLL into your project.
What can you do once you include MFC in your DLL? You can, in fact, use almost all of MFC within your COM server. This includes such very helpful classes as CString and MFC’s collection classes. You also get the wrappers for device contexts (the CDC family), the CMenu class, CCmdTarget, MFC’s message mapping architecture, MFC’s window and dialog support, MFC’s document/view architecture, and MFC’s support for automation.
This makes it very easy to develop COM-based components that support a user interface. For example, if you simply must include a dialog inside your COM class, there’s no more convenient way than to whip one up using the Visual C++ resource editor, creating a CDialog class using ClassWizard. Then just include the dialog header wherever you want to display the dialog, create an instance of the dialog class, and call DoModal. In addition, MFC developers (ourselves included) are quite used to CString and those handy collection classes. It’s sure hard to give them up. Just mark the Support MFC checkbox and it’s all there for you.
Unfortunately, all this comes with a price. The minute you include MFC support in your ATL COM-based DLL, your COM DLL has to start lugging around the MFC runtime DLL. That’s just the problem ATL is trying to solve, isn’t it? In addition, because the MFC AppWizard didn’t create the project, there’s no ODL file (the MFC AppWizard pumps out an ODL file whenever you check the Automation checkbox). Therefore ClassWizard chokes whenever you want to use it to implement IDispatch (by going to the Automation tab). ClassWizard looks for the project name.odl file and displays a warning if it’s missing. But you do get all the comforts of the MFC classes, so using MFC in your ATL class is sometimes justified.
The next checkbox option available through the ATL COM AppWizard is the ability to merge the proxy/stub code into your COM DLL. A major goal of COM is that clients be able to use an object in the same way regardless of its location. To accomplish this feat, COM’s remoting architecture uses proxy/stub pairs. Whenever a client calls QueryInterface on an object living in another execution context (perhaps another process or even a remote machine), the remoting layer installs an interface proxy on the client side and an interface stub on the server side. On the client side, the proxy looks, feels, and tastes like the COM interface the client expects. However, rather than talking to the COM object directly (which would happen if the object were executing in the same process and thread), the client talks to the proxy. The proxy in turn talks to the remoting layer to make function calls into the other execution context. On the server side, the stub receives calls from the proxy and sets up the call in the other execution context. Then the stub actually calls into the object to do the work. The stub then sends any results back to the proxy, which in turn gets the results back to the client.
Normally, proxy/stub pairs are a product of first compiling some IDL code to get some C source code that compiles into a DLL containing the proxy/stub pairs. By default, the ATL COM AppWizard generates the C source code for a proxy/stub DLL based on the contents of the project’s IDL file. Of course this produces a separate proxy/stub DLL for your interfaces. If you’d like, you can store the proxy/stub pairs inside your COM-based DLL. Do this by checking the “Allow merging of proxy/stub code” option in the ATL COM AppWizard.
Checking “Allow merging of proxy/stub code” generates the same files as a regular COM DLL with the addition of two more files: dlldatax.c and dlldatax.h. Also, the file project name.cpp is somewhat larger to handle requests for the proxy. Whether or not you want to merge the proxy/stub code into your DLL is controlled by the precompiler definition _MERGE_PROXYSTUB. To produce a single DLL that has your COM classes and the proxy/stub pair for remoting your interface, simply add dlldatax.c to the project and add _MERGE_PROXYSTUB to the list of precompiler definitions. Depending on your environment (perhaps you’re running Windows® 95 without DCOM) you may have to turn off the following #define:
#define _WIN32_WINNT 0x0400
If you want a separate proxy/stub DLL, you can run NMAKE by itself to produce a separate DLL. The DLL will produce all the correct registry entries once it’s registered.
Producing an EXE server is a little more straightforward because the Support MFC and “Allow merging of proxy/stub code” options are turned off. An EXE server runs in its own process space, has its own primary thread, and services the message queue. As with DLL servers, EXE servers perform the correct contortions to be COM servers. However, instead of exporting the well-known functions for exposing class objects, an EXE server exposes its class objects via the COM API function CoRegisterClassObject. Instead of exporting DllRegisterServer and DllUnregisterServer, ATL COM-based EXEs check the command line for /REGSERVER or /UNREGSERVER to find out whether they should install information in the registry. Finally, rather than being unloaded passively (as DLLs are), an EXE server watches its reference count go up and down. When the reference count drops to zero, the EXE server posts a WM_QUIT message to itself. All the DLL-versus-EXE-specific operations occur within the server’s CComModule-derived object.
As with a DLL server, an EXE server generated by the ATL COM AppWizard also includes IDL code. Compiling the EXE yields enough source code to create a proxy/stub DLL capable of remoting the interfaces described within the IDL file.
The final option for an ATL server is a COM-based Windows NT® service. The main difference between a regular EXE server and service is that the ATL COM AppWizard generates a class named CServiceModule, derived from CComModule, to manage the COM and service aspects of the server. CServiceModule includes four critical member functions necessary for the EXE to run as a service: Start, ServiceMain, Run, and Handler.
The server’s WinMain calls CServiceModule::Start to kick off the server. Start calls StartServiceCtrlDispatcher to connect the main thread of the service to the Service Control Manager (SCM). Start passes CServiceModule::ServiceMain as one of the parameters to StartServiceCtrlDispatcher.
CServiceModule::ServiceMain is called whenever the service is started. ServiceMain calls back to the SCM using RegisterServiceCtrlHandler to pass CServiceModule::Handler to the SCM. CServiceModule::Handler is called by the SCM. By default, ATL handles the stop instruction (and posts a WM_QUIT message to itself). ServiceMain also calls the API function SetServiceStatus to tell the SCM that the service is starting. Finally CServiceModule::Run runs the service. Run calls CoInitialize, and then starts the message pump.
That does it for the options available through the ATL COM AppWizard. Let’s take a look at the options available through the ATL Object Wizard.
The ATL Object Wizard (see Figure 2) is an extremely convenient way to add a COM class to your ATL COM-based server. In Developer Studio, just select New ATL Object from the Insert menu. The Properties dialog allows you to select the threading model for your COM class, whether you want a dual (IDispatch-based) or just a custom interface, and how your class will support aggregation. In addition, the ATL Object Wizard lets you easily include the ISupportErrorInfo interface and connection points in your object. Finally, you can choose to aggregate to the freethreaded marshaler.
Let’s start by taking a look at ATL’s support for the various threading models.
Figure 2: The ATL Object Wizard options
COM is based on the notion of abstraction—hiding as much information as possible from the client. One piece of information that COM hides from the client is whether the COM class is thread-safe. The client should be able to use an object as it sees fit without having to worry about whether an object serializes access to itself properly. In COM, an apartment provides this abstraction.
An apartment defines an execution context that houses interface pointers. A thread (an execution context) enters an apartment by calling a function from the CoInitialize family (CoInitialize, CoInitializeEx, or OleInitialize). Then COM requires that all method calls to an interface pointer be executed within the apartment that initialized the pointer (for instance, from the same thread that called CoInitializeEx). COM defines two kinds of apartments—single-threaded apartments (STA) and multithreaded apartments (MTA). While a process may have only one multithreaded apartment, a process may have many single-threaded apartments. Furthermore, an apartment may house any number of COM objects.
A COM object created within a single-threaded apartment is guaranteed to have its method calls serialized through the remoting layer. A COM object created within the multithreaded apartment is not. Instantiating a COM object within the multithreaded apartment is like putting a piece of data into the global scope where multiple threads can get to it. Putting a COM class within a single-threaded apartment is like having data within the scope of only one thread. The bottom line is that a COM class that wants to live in the multithreaded apartment had better be thread-safe, while a COM class that is satisfied living in its own apartment need not worry about concurrent access to its data. While access to a COM object living in the same apartment as the client is extremely fast, accessing a COM object in a different apartment than the client incurs the cost of remoting (which is much slower).
As mentioned, an STA COM object that lives within a different process space from its client has its method calls serialized automatically via the remoting layer. For a COM class that lives in an MTA, you may want to provide internal protection (using critical sections for example) rather than having the remoting layer protect it. An inprocess COM class advertises its thread safety to the world via a registry setting:
[HKCR\CLSID\{some GUID …}\InprocServer32]
@="C:\SomeServer.DLL"
ThreadingModel=<thread model>
ThreadingModel can be one of four values: Single, Both, Free, or Apartment. The named value may also be blank, which indicates the class executes in the primary STA only. (The primary STA is the thread in the process that called CoInitialize first.) Both indicates the class is thread-safe and may execute in both apartments. Using this value tells COM to use the same kind of apartment as the client. Free indicates that the class is thread-safe. Using this value tells COM to force the object inside the multithreaded apartment. Apartment indicates the thread isn’t thread-safe and must live in its own STA. As you can see from Figure 2, ATL supports all current threading models.
Choosing threading models from the ATL Object Wizard inserts different code into your class depending upon what you select. For example, if you select the Apartment option, the ATL Object Wizard derives your class from CComObjectRootEx and includes CComSingleThreadModel as the template parameter:
class ATL_NO_VTABLE CApartmentOb :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CApartmentOb,
&CLSID_ApartmentOb >,
public IDispatchImpl<IApartmentOb,
&IID_CApartmentOb, &LIBID_DLLSVRLib> {
};
The CComSingleThreadModel template parameter mixes in the more efficient standard increment and decrement operations for IUnknown (because access to the class is automatically serialized). In addition, the ATL Object Wizard causes the class to insert the correct threading model value in the registry. Choosing the Single option also causes the class to use the CComSingleThreadModel and leaves the threading model value blank in the registry.
Selecting Both or Free causes the class to use the CComMultiThreadModel template parameter, which employs the thread-safe Win32® increment and decrement operations InterlockedIncrement and InterlockedDecrement. A freethreaded class definition looks like this:
class ATL_NO_VTABLE CFreeOb :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CFreeOb, &CLSID_FreeOb>,
public IDispatchImpl<IFreeOb, &IID_IFreeOb,
&LIBID_DLLSVRLib>
Choosing Both for your threading model inserts Both as the named value for the threading model, while choosing Free uses the named value of Free for the threading model.
The last two options worth covering include connection points, ISupportErrorInfo, and the freethreaded marshaler.
Adding connection points to your class (for example an event set) is simply a matter of defining the callback interface in the IDL file, using the ATL proxy generator to create a proxy, adding the proxy class to the COM class, and adding the connection points to the connection point map. Adding connection to your COM class is easy. Marking the Support Connection Points checkbox (see Figure 2) causes the class to derive from IConnectionPointImpl. This option also adds a blank connection map to your class.
ATL also includes support for ISupportErrorInfo, which ensures that error information is propagated up the call chain correctly. OLE Automation objects that use the error handling interfaces must implement ISupportErrorInfo. Clicking on Support ISupportErrorInfo in the ATL Object Wizard dialog box causes the ATL-based class to derive from ISupportErrorInfoImpl.
Checking the Free Threaded Marshaler option aggregates the COM freethreaded marshaler to your class. The generated class does this by calling CoCreateFreeThreadedMarshaler in the class’s FinalConstruct function. The freethreaded marshaler allows objects that are thread-safe to bypass the standard marshaling that occurs whenever cross-apartment interface methods are invoked. That way threads living in one apartment can access interface methods in another apartment as though they were in the same apartment, speeding up cross-apartment calls tremendously. The freethreaded marshaler does this by implementing IMarshal. When the client asks the object for an interface, the remoting layer turns around and calls QueryInterface asking for IMarshal. If the object implements IMarshal (because the ATL Object Wizard also adds an entry into the class’s interface to handle QueryInterface requests for IMarshal) and the marshaling request is in process, then the freethreaded marshaler actually copies the pointer into the marshaling packet. That way the client receives a real live pointer to the object. The client talks to the object directly without having to go through proxies and stubs. Of course, if you choose this option, all your data in your object had better be thread-safe. Also, if you choose this option for your object, the object itself should not hold pointers (either direct or proxy) to other objects outside of its apartment.
ATL provides welcome relief to developers of COM objects. Just as MFC more or less renders writing WndProcs unnecessary, ATL saves you from writing IUnknown and the related COM support functions over and over again. In addition, the ATL COM AppWizard gives you most of the options you’d want for developing a variety of different servers. All you need to do is get to know the ATL COM AppWizard a bit more. Unfortunately, the ATL documentation doesn’t completely describe what each of the checkboxes and buttons does. Now that you understand these ATL COM AppWizard and ATL Object Wizard options better, you’re more prepared to use ATL to its fullest.
Have a question about programming in Visual Basic, Visual FoxPro, Microsoft Access, Office or stuff like that? Send your questions via email to George Shepherd at 70023.1000@compuserve.com