COM-based DLLs have several distinguishing characteristics. First, COM-based DLLs export a function that the service control manager (SCM) requires in order to locate the class objects within the DLL (DllGetClassObject). COM-based DLLs also maintain a reference count on themselves. Clients have the option of flushing the DLL from its process space by calling CoFreeUnusedLibraries. When a client calls CoFreeUnusedLibraries, COM goes to each DLL in the process space and asks the DLL whether the DLL can be unloaded. COM calls into the DLL's DllCanUnloadNow function, which returns S_OK if the DLL can be unloaded and S_FALSE if the DLL can't be unloaded. (The DLL simply checks its reference count.) Finally, COM-based DLL servers are supposed to self-register and self-unregister themselves in the Registry by exporting two functions—DllRegisterServer and DllUnregisterServer. These are the hooks that COM expects to see in your DLL.
The ATL COM AppWizard's default mode is to create a COM-based DLL. The COM-specific aspects of a DLL include the following four well-known entry points:
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. The proxy/stub source code consists of three more files:
As far as clients are concerned, an ATL-based COM DLL is just a regular COM DLL. The difference is that many of the implementation details are handled internally by ATL. Let's take a closer look at the ATL COM AppWizard options.
One of the most interesting aspects of ATL is that although ATL is meant to be a lightweight alternative to MFC for creating ActiveX controls, you can include MFC in your ATL-based DLL. Just mark the Support MFC check box before building your DLL. Clicking on Support MFC within the ATL COM AppWizard simply inserts two more 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.
Besides adding the include files, the AppWizard links the import library for the MFC run-time 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 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.
If you're a seasoned MFC developer and you're used to having all these goodies, you might find this useful. 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 Visual C++'s resource editor and create 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, many MFC developers are quite used to CString and those handy collection classes. It's hard for some developers to give them up. Just mark the Support MFC check box and it's all there for you.
Unfortunately, this MFC support comes at a price. The minute you include MFC support in your ATL-based COM DLL, your COM DLL has to start lugging around the MFC run-time DLL. Wait a minute—that's just the problem COM is trying to solve, isn't it? In addition to this problem, because the MFC AppWizard didn't create the project, there's no Object Description Language (ODL) file. (The MFC AppWizard pumps out an ODL file whenever you check the Automation check box.) 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, and using MFC in your ATL class is sometimes justified—especially if you want to have a whole lot of legacy MFC source code and the MFC DLL will be loaded into memory anyway.
The next check box available through the AppWizard offers the ability to merge the proxy/stubs into your COM DLL.
A major goal of COM is for clients to 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 that the client expects. However, rather than talking to the COM object directly (which would happen if the object executed 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 in to the object to do the work. The stub sends any results back to the proxy, which in turn gets the results back to the client.
We first discussed merging proxy/stub pairs in Chapter 4. We're repeating some of the same information here in case you just skimmed that chapter.
Normally, proxy/stub pairs are a product of first compiling some IDL code to get some C source code. Then you take that source code and compile it 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 like, you can choose to 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 plus 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 objects 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 or Windows 98 without DCOM, for example) you might have to turn off the #define _WIN32_WINNT 0x0400 symbol. 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 is registered.
The ATL COM AppWizard includes one more option as far as DLLs are concerned—creating a Microsoft Transaction Server (MTS) DLL.
The MTS option adds only a little bit to your project: Microsoft Transaction Server's import library (MTX.LIB) and the GUIDs necessary for doing MTS work. Choosing this option also causes your object to register in the MTS catalog.