Real COM with the MFC Library
So much for simulations. Now we'll get ready to convert the spaceship example to genuine COM. You need to acquire a little more knowledge before we start, though. First, you must learn about the CoGetClassObject function, then you must learn how COM uses the Windows Registry to load the component, and then you have to understand the difference between an in-process component (a DLL) and an out-of-process component (an EXE or a DLL running as a surrogate). Finally, you must become familiar with the MFC macros that support nested classes.The net result will be an MFC regular DLL component that contains all the CSpaceship code with the IMotion and IVisual interfaces. A regular MFC library Windows application will act as the client. It will load and run the component when the user chooses a menu command.
The COM CoGetClassObject Function
In our simulation, we used a phony function named GetClassObject. In real COM, we'll use the global CoGetClassObject function. (Co stands for "component object.") Compare the following prototype to the GetClassObject function you saw earlier:
STDAPI CoGetClassObject(REFCLSID rclsid, DWORD dwClsContext,
COSERVERINFO* pServerInfo, REFIID riid, LPVOID* ppvObj)
The interface pointer goes in the ppvObj parameter, and pServerInfo is a pointer to a machine on which the class object is instantiated (NULL if the machine is local). The types REFCLSID and REFIID are references to 128-bit globally unique identifiers (GUIDs) for COM classes and interfaces. STDAPI indicates that the function returns a 32-bit value of type HRESULT.The standard GUIDs (for example, those that name interfaces that Microsoft has created) are defined in the Windows libraries that are dynamically linked to your program. You must define GUIDs for custom classes and interfaces, such as those for spaceship objects, in this way:
// {692D03A4-C689-11CE-B337-88EA36DE9E4E}
static const IID IID_IMotion =
{0x692d03a4, 0xc689, 0x11ce, {0xb3, 0x37, 0x88, 0xea, 0x36,
0xde, 0x9e, 0x4e}};
If the dwClsContext parameter is CLSCTX_INPROC_SERVER, the COM subsytem will look for a DLL. If the parameter is CLSCTX_LOCAL_SERVER, COM will look for an EXE. The two flags can be ORed together to indicate loading of a DLL if the DLL is available or an EXE if the DLL isn't available. For example, in-process servers are fastest because everybody shares the same address space. Communication EXE servers are considerably slower because the interprocess calls involve data copying as well as many thread context switches. The return value is an HRESULT value, which is 0 (NOERROR) if no error occurs.
Note | Another COM function, CoCreateInstance, combines the functionality of CoGetClassObject and IClassFactory::CreateInstance. |
COM and the Windows Registry
In the Ex22a example, the component is statically linked to the client—a clearly bogus circumstance. In real COM, the component is either a DLL or a separate EXE. When the client calls the CoGetClassObject function, COM steps in and finds the correct component, which is located somewhere on disk. How does COM make the connection? It looks up the class's unique 128-bit class ID number in the Windows Registry. Thus, the class must be registered permanently on your computer.If you run the Windows Regedit program, you'll see a screen similar to the one shown in Figure 22-1. This figure shows subfolders for four class IDs, three of which are class IDs associated with DLLs (InprocServer32) and one of which is a class ID associated with an EXE (LocalServer32). The CoGetClassObject function looks up the class ID in the Registry and then loads the DLL or EXE as required.

Figure 22-1: Subfolders of four class IDs in the Registry.
What if you don't want to track those ugly class ID numbers in your client program? No problem. COM supports another type of registration database entry that translates a human-readable program ID into the corresponding class ID. Figure 22-2 shows the Registry entries. The COM function CLSIDFromProgID reads the database and performs the translation.

Figure 22-2: Human-readable program IDs in the Registry.
The first CLSIDFromProgID parameter is a string that holds the program ID, but it's not an ordinary string. This is your first exposure to double-byte characters in COM. All string parameters of COM functions (except Data Access Objects [DAO]) are Unicode character string pointers of type OLECHAR*. The constant need to convert between double-byte strings and ordinary strings will make your life miserable. If you need a double-byte literal string, prefix the string with an L character, like this:
CLSIDFromProgID(L"Spaceship", &clsid);
You'll begin learning about the MFC library's Unicode string conversion capabilities in Chapter 23.How does the registration information get into the Registry? You can program your component application to call Windows functions that directly update the Registry. The MFC library conveniently wraps these functions with the function COleObjectFactory::UpdateRegistryAll, which finds all your program's global class factory objects and registers their names and class IDs.
Runtime Object Registration
You've just seen how the Windows Registry registers COM classes on disk. Class factory objects also must be registered in memory for out-of-process servers. It's unfortunate that the word register is used in both contexts. Objects in out-of-process component modules are registered at run time with a call to the COM CoRegisterClassObject function, and the registration information is maintained in memory by the Windows DLLs. If the factory is registered in a mode that permits a single instance of the component module to create multiple COM objects, COM can use an existing process when a client calls CoGetClassObject.
How a COM Client Calls an In-Process Component
We're beginning with a DLL component instead of an EXE component because the program interactions are simpler. I'll show pseudocode here because you'll be using the MFC library classes, which hide much of the detail.
Client | |
CLSID clsid; | |
IClassFactory* pClf; | |
IUnknown* pUnk; | |
CoInitialize(NULL); // Initialize COM | |
CLSIDFromProgID("componentname", &clsid); | |
COM | |
COM uses the Registry to look up the class ID from "componentname" | |
Client | |
CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, | |
IID_IClassFactory, (void**) &pClf ); | |
COM | |
COM uses the class ID to look for a component in memory if (component DLL is not loaded already) | |
{ | |
COM gets DLL filename from the Registry | |
COM loads the component DLL into process memory | |
} | |
DLL Component | |
if (component just loaded) { | |
Global factory objects are constructed | |
DLL's InitInstance called (MFC only) | |
} | |
COM | |
COM calls DLL's global exported DllGetClassObject with the CLSID value that was passed to CoGetClassObject | |
DLL Component | |
DllGetClassObject returns IClassFactory* | |
COM | |
COM returns IClassFactory* to client | |
Client | |
pClf->CreateInstance (NULL, IID_IUnknown, (void**) &pUnk); | |
DLL Component | |
Class factory's CreateInstance function called (called directly—through component's vtable) | |
Constructs object of "componentname" class | |
Returns requested interface pointer | |
Client | |
pClf->Release(); | |
pUnk->Release(); | |
DLL Component | |
"componentname" Release is called through vtable | |
if (refcount == 0) { | |
Object destroys itself | |
} | |
Client | |
CoFreeUnusedLibraries(); | |
COM | |
COM calls DLL's global exported DllCanUnloadNow | |
DLL Component | |
DllCanUnloadNow called if (all DLL's objects destroyed) { | |
return TRUE | |
} | |
Client | |
CoUninitialize(); // COM frees the DLL if DllCanUnloadNow returns TRUE just prior to exit | |
COM | |
COM releases resources | |
Client | |
Client exits | |
DLL Component | |
Windows unloads the DLL if it is still loaded and no other programs are using it |
Some important points to note: First, the DLL's exported DllGetClassObject function is called in response to the client's CoGetClassObject call. Second, the class factory interface address returned is the actual physical address of the class factory vtable pointer in the DLL. Third, when the client calls CreateInstance—or any other interface function—the call is direct (through the component's vtable).
The COM linkage between a client EXE and a component DLL is quite efficient—as efficient as the linkage to any C++ virtual function in the same process, plus the full C++ parameter and return type-checking at compile time. The only penalty for using ordinary DLL linkage is the extra step of looking up the class ID in the Registry when the DLL is first loaded.
How a COM Client Calls an Out-of-Process Component
The COM linkage to a separate EXE component is more complicated than the linkage to a DLL component. The EXE component is in a different process, or possibly on a different computer. Don't worry, though. You should write your programs as if a direct connection existed. COM takes care of the details through its remoting architecture, which usually involves RPCs.In an RPC, the client makes calls to a special DLL called a proxy. The proxy sends a stream of data to a stub, which is inside a DLL in the component's process. When the client calls a component function, the proxy alerts the stub by sending a message to the component program, which is processed by a hidden window. The mechanism of converting parameters to and from data streams is called marshaling.If you use standard interfaces (those defined by Microsoft) such as IClassFactory and IPersist (an interface we'll look at later when we examine COM persistence), the proxy and the stub code, which implement marshaling, are provided by the Windows OLEAUT32 DLL. If you invent your own interfaces, such as IMotion and IVisual, you must write the proxies and stubs yourself. Fortunately, creating proxy and stub classes involves simply defining your interfaces in Interface Definition Language (IDL) and compiling the code produced by the Microsoft Interface Definition Language (MIDL) compiler.
Here's the pseudocode interaction between an EXE client and an EXE component. Compare it to the DLL version beginning on page 554. Notice that the client-side calls are exactly the same.
Client | |
CLSID clsid; | |
IClassFactory* pClf; | |
IUnknown* pUnk; | |
CoInitialize(NULL); // Initialize COM | |
CLSIDFromProgID("componentname", &clsid); | |
COM | |
COM uses the Registry to look up the class ID from "componentname" | |
Client | |
CoGetClassObject(clsid, CLSCTX_LOCAL_SERVER, NULL, | |
IID_IClassFactory, (void**) &pClf); | |
COM | |
COM uses the class ID to look for a component in memory if (component EXE is not loaded already, or if we need another instance) { | |
COM gets EXE filename from the Registry | |
COM loads the component EXE | |
} | |
EXE Component | |
if (just loaded) { | |
Global factory objects are constructed | |
InitInstance called (MFC only) | |
CoInitialize(NULL); | |
for each factory object { | |
CoRegisterClassObject(...); | |
Returns IClassFactory* to COM | |
} | |
} | |
COM | |
COM returns the requested interface pointer to the client | |
(client's pointer is not the same as the component's interface pointer) | |
Client | |
pClf->CreateInstance(NULL, IID_IUnknown, (void**) &pUnk); | |
EXE Component | |
Class factory's CreateInstance function called | |
(called indirectly through marshaling) | |
Constructs object of "componentname" class | |
Returns requested interface pointer indirectly | |
Client | |
pClf->Release(); | |
pUnk->Release(); | |
EXE Component | |
"componentname" Release is called indirectly | |
if (refcount == 0) { | |
Object destroys itself | |
} | |
if (all objects released) { | |
Component exits gracefully | |
} | |
Client | |
CoUninitialize(); // just prior to exit | |
COM | |
COM calls Release for any objects this client has failed to release | |
EXE Component | |
Component exits | |
COM | |
COM releases resources | |
Client | |
Client exits |
As you can see, COM plays an important role in the communication between the client and the component. COM keeps an in-memory list of class factories that are in active EXE components, but it does not keep track of individual COM objects such as the CSpaceship object. Individual COM objects are responsible for updating the reference count and for destroying themselves through the AddRef/Release mechanism. COM does step in when a client exits. If that client is using an out-of-process component, COM will "listen in" on the communication and keep track of the reference count on each object. COM will disconnect from component objects when the client exits. Under certain circumstances, this will cause those objects to be released. Don't depend on this behavior, however. Be sure that your client program releases all its interface pointers before exiting.
The MFC Interface Macros
In Ex22a, you saw nested classes used for interface implementation. The MFC library has a set of macros that automate this process. For the CSpaceship class, which is derived from the real MFC CCmdTarget class, you use these macros inside the declaration:
BEGIN_INTERFACE_PART(Motion, IMotion)
STDMETHOD_(void, Fly) ();
STDMETHOD_(int&, GetPosition) ();
END_INTERFACE_PART(Motion)
BEGIN_INTERFACE_PART(Visual, IVisual)
STDMETHOD_(void, Display) ();
END_INTERFACE_PART(Visual)
DECLARE_INTERFACE_MAP()
The INTERFACE_PART macros generate the nested classes, adding X to the first parameter to form the class name and adding m_x to form the embedded object name. The macros generate prototypes for the specified interface functions plus prototypes for QueryInterface, AddRef, and Release.The DECLARE_INTERFACE_MAP macro generates the declarations for a table that holds the IDs of all the class's interfaces. The CCmdTarget::ExternalQueryInterface function uses the table to retrieve the interface pointers.In the CSpaceship implementation file, use the following macros:
BEGIN_INTERFACE_MAP(CSpaceship, CCmdTarget)
INTERFACE_PART(CSpaceship, IID_IMotion, Motion)
INTERFACE_PART(CSpaceship, IID_IVisual, Visual)
END_INTERFACE_MAP()
These macros build the interface table used by CCmdTarget::ExternalQueryInterface. A typical interface member function looks like this:
STDMETHODIMP_(void) CSpaceship::XMotion::Fly()
{
METHOD_PROLOGUE(CSpaceship, Motion)
pThis->m_nPosition += 10;
return;
}
Don't forget that you must implement all the functions for each interface, including QueryInterface, AddRef, and Release. Those three functions can delegate to functions in CCmdTarget.
Note | The STDMETHOD_ and STDMETHODIMP_ macros declare and implement functions using the __stdcall parameter passing convention, as required by COM. These macros allow you to specify the return value as the first parameter. Two other macros, STDMETHOD and STDMETHODIMP, assume an HRESULT return value. |
The MFC COleObjectFactory Class
In the simulated COM example, you saw a CSpaceshipFactory class that was hard-coded to generate CSpaceship objects. The MFC library applies its dynamic creation technology to the problem. Thus, a single class, aptly named COleObjectFactory, can create objects of any class specified at run time. All you need to do is use macros like these in the class declaration:
DECLARE_DYNCREATE(CSpaceship)
DECLARE_OLECREATE(CSpaceship)
And use macros like these in the implementation file:
IMPLEMENT_DYNCREATE(CSpaceship, CCmdTarget)
// {692D03A3-C689-11CE-B337-88EA36DE9E4E}
IMPLEMENT_OLECREATE(CSpaceship, "Spaceship", 0x692d03a3, 0xc689, 0x11ce,
0xb3, 0x37, 0x88, 0xea, 0x36, 0xde, 0x9e, 0x4e)
The DYNCREATE macros set up the standard dynamic creation mechanism. The OLECREATE macros declare and define a global object of class COleObjectFactory with the specified unique CLSID. In a DLL component, the exported DllGetClassObject function finds the specified class factory object and returns a pointer to it based on global variables set by the OLECREATE macros. In an EXE component, initialization code calls the static COleObjectFactory::RegisterAll, which finds all factory objects and registers each one by calling CoRegisterClassObject. The RegisterAll function is also called when a DLL is initialized. In that case, it merely sets a flag in the factory object(s).We've really just scratched the surface of MFC's COM support. If you need more details, see Shepherd and Wingo's MFC Internals.
Wizard Support for COM In-Process Components
The MFC DLL Wizard isn't optimized for creating COM DLL components, but you can add COM support to your DLLs by requesting a regular DLL with Automation support. (Select Automation on the Application Settings page.) The following functions in the project's main source file are of interest:
BOOL CEx22bApp::InitInstance()
{
CWinApp::InitInstance();
// Register all OLE server (factories) as running. This enables the
// OLE libraries to create objects from other applications.
COleObjectFactory::RegisterAll();
return TRUE;
}
// DllGetClassObject - Returns class factory
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return AfxDllGetClassObject(rclsid, riid, ppv);
}
// DllCanUnloadNow - Allows COM to unload DLL
STDAPI DllCanUnloadNow(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return AfxDllCanUnloadNow();
}
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid))
return SELFREG_E_TYPELIB;
if (!COleObjectFactory::UpdateRegistryAll())
return SELFREG_E_CLASS;
return S_OK;
}
// DllUnregisterServer - Removes entries from the system registry
STDAPI DllUnregisterServer(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))
return SELFREG_E_TYPELIB;
if (!COleObjectFactory::UpdateRegistryAll(FALSE))
return SELFREG_E_CLASS;
return S_OK;
}
The four global functions are exported in the project's DEF file. By calling MFC functions, you ensure that the global functions do everything you need in a COM in-process component. The DllRegisterServer and DllUnregisterServer functions can be called by a utility program to update the system Registry.
Once you've created the skeleton project, your next step is to use MFC Class Wizard to add one or more COM-creatable classes to the project. Specify the class name, the base class, and filenames for the new class on the Names page, as shown here:

In your generated class, you end up with some Automation elements such as dispatch maps, but you can safely remove them. You can also remove the following two lines from

#include <afxodlgs.h>
#include <afxdisp.h>
MFC COM Client Programs
Writing an MFC COM client program is a no-brainer. You just use the MFC Application Wizard to generate a normal application, and then you add the following line in

#include <afxole.h>
Next, add the following line at the beginning of the application class InitInstance member function:
AfxOleInit();
You're now ready to add code that calls CoGetClassObject.