Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
With the addition of a Simple Object, the ATL Object Wizard has added three new files to your project workspace:
-
CoHexagon.h The header file for CoHexagon.
-
CoHexagon.cpp The implementation file for CoHexagon.
-
CoHexagon.rgs The registry script file for CoHexagon.
Before examining the source code, let's recap the basics of what ATL will be supplying for us:
-
ATL will provide a default implementation of IUnknown for your coclasses. This support is provided by CComObjectRootBase, CComObjectRootEx<>, and your class's COM map.
-
ATL will provide a generic class factory to create your objects. CComCoClass<> defines a generic class factory for this purpose.
-
ATL will provide self-registration support for each coclass in the server. This is achieved with your server's registry script file, CComModule, and a handful of registration macros.
Examining CCoHexagon.h
Here is the header file for our ATL-ized CoHexagon coclass:
// A typical header file for an ATL Simple Object. class ATL_NO_VTABLE CCoHexagon : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoHexagon, &CLSID_CoHexagon>, public IDraw { public: CCoHexagon() { } DECLARE_REGISTRY_RESOURCEID(IDR_COHEXAGON) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CCoHexagon) COM_INTERFACE_ENTRY(IDraw) END_COM_MAP() // IDraw public: };
Notice that your simple CoHexagon derives directly from two ATL templates. These ATL templates specify CoHexagon's threading model, level of aggregation support, and the associated class factory as well as support for IUnknown. Here is a rundown of the functionality provided by these base classes, which we will explore in much greater detail in later chapters:
An Overview of CComObjectRootEx<>
Although you can't tell from your class's inheritance chain, CComObjectRootEx<> is derived from another ATL class, CComObjectRootBase. These two ATL classes are used to provide helper functions called by the IUnknown implementation for your coclass. As we will see later in this section, your coclass is not the most derived class, but is actually passed as a template parameter to another ATL class (most often CComObject<>) which implements the three methods of IUnknown.
CComObjectRootBase and CComObjectRootEx<> provide member functions that are accessed by CComObject<> to finalize the implementation of QueryInterface(), AddRef(), and Release(). CComObjectRootBase defines the "look and feel" for QueryInterface(). CComObjectRootEx<> fills out the IUnknown specification by adding helper functions used for the implementation of AddRef() and Release().
The single template parameter given to CComObjectRootEx<> is one of a handful of threading model classes defined by ATL. Using this parameter, your object's reference counting implementation will specified with just enough thread safety. We will cover COM threading in Chapter 7, but realize that the template parameter used by CComObjectRootEx<> is determined by your threading model selection from the Attribute tab of the ATL Object Wizard.
An Overview of the ATL COM Map
In order for ATL's IUnknown implementation to function correctly, your coclass must define a COM map. The COM map is established with the BEGIN_COM_MAP and END_COM_MAP macro set. An object's COM map contains a list of all interfaces supported by the coclass. Keep in mind that if you do not list an interface in the COM map, a client will not be able to access it via QueryInterface. IUnknown is the exception with the COM map, as the IUnknown pointer is calculated indirectly using the first entry in the COM map and therefore you will not explicitly find a listing. As you can see, the ATL Object Wizard has already listed support for IDraw with the COM_INTERFACE_ENTRY macro:
// ATL COM maps provide a look-up table defining each supported interface // in the coclass. CComObjectRootBase must find this map in your derived class. BEGIN_COM_MAP(CCoHexagon) COM_INTERFACE_ENTRY(IDraw) END_COM_MAP()
The ATL COM map may be populated by 17 different interface macros (which we cover in Chapter 8). Many of these are only useful when building a coclass using some advanced COM techniques such as aggregation or tear-off interfaces. By and large, the most common entry is COM_INTERFACE_ENTRY, which will return the vPtr for the specified interface.
Note | Note that the COM_INTERFACE_ENTRY macro takes the IDL name of the interface (IDraw, IShapeEdit, IStats, and so forth), and not the IID associated to it. |
An Overview of CComCoClass<>
The second ATL template from which you derive specifies the class object for the class. The definition of CComCoClass<> specifies the DECLARE_CLASSFACTORY macro, which expands to define a default class factory implementing IClassFactory. As well, CComCoClass<> specifies how CoHexagon should behave if it is asked by another object to serve as an aggregate. Recall that aggregation is a COM reuse mechanism, by which an "outer" object creates and maintains an "inner" object. The inner object's interfaces are exposed by the outer object, providing the illusion that the outer object is composed of more interfaces than it actually is. We will revisit COM aggregation (and discover why you might want to do this) later in Chapter 8. For now, just realize that CComCoClass<> defines the DECLARE_AGGREGATABLE macro which tells the world that CoHexagon can function as an aggregated object if asked to do so, but is not required to do so.
Examining CCoHexagon.cpp
Your CoHexagon implementation file is completely empty as of now (beyond #include statements), as we have not yet fleshed out the methods of IDraw:
// CoHexagon.cpp : Implementation of CCoHexagon #include "stdafx.h" #include "ATLShapesServer.h" #include "CoHexagon.h"
Examining CoHexagon.rgs
As mentioned, a self-registering COM server understands what to do when told to register and unregister itself. In ATL, CComModule is the class in charge of entering (or removing) the necessary registry information for each coclass listed in the object map. When performing registration duties, CComModule will be on the lookout for the DECLARE_REG- ISTRY_RESOURCEID macro in your class's header file. This macro expands to provide an implementation of the UpdateRegistry() method, and is called by CComModule during the registration and unregistration of your coclasses. The sole parameter to DECLARE_REGISTRY_ RESOURCEID is the resource ID of your server's binary RGS file. If you examine your ResourceView tab, you will see a new custom resource ("REGISTRY") subfolder has appeared (Figure 6-18).
ATL's Registry Scripting Language
So what is an RGS (ReGistry Script) file? ATL supplies a simple and elegant scripting language to specify what to insert (or remove) into (or from) the system registry during the installation process. This registry scripting language removes the need to maintain a messy REG file to register your COM servers. By default, an RGS file specifies a version- dependent ProgID, and version-independent ProgID and CLSID entries. Your server's interfaces and TypeLib information are entered programmatically. Here is the RGS file for CoHexagon:
HKCR { ATLShapesServer.CoHexagon.1 = s 'CoHexagon Class' { CLSID = s '{4A01DD03-066C-11D3-B8E5-0020781238D4}' } ATLShapesServer.CoHexagon = s 'CoHexagon Class' { CLSID = s '{4A01DD03-066C-11D3-B8E5-0020781238D4}' CurVer = s 'ATLShapesServer.CoHexagon.1' } NoRemove CLSID { ForceRemove {4A01DD03-066C-11D3-B8E5-0020781238D4} = s 'CoHexagon Class' { ProgID = s 'ATLShapesServer.CoHexagon.1' VersionIndependentProgID = s 'ATLShapesServer.CoHexagon' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } 'TypeLib' = s '{13AE1FB1-1582-11D3-B8F2-0020781238D4}' } } }
As you can see, RGS syntax is modeled after a standard tree structure, with each node identified by an open/close bracket pair ( {} ). The topmost node (in this case HKCR) is used to identify which hive will be modified by the script. RGS syntax allows you to write to any hive in the registry, and may take any of the following values:
Hive Acronym | Meaning in Life |
---|---|
HKCR | HKEY_CLASSES_ROOT |
HKCU | HKEY_CURRENT_USER |
HKLM | HKEY_LOCAL_MACHINE |
HKU | HKEY_USERS |
HKPD | HKEY_PERFORMANCE_DATA |
HKDD | HKEY_DYN_DATA |
HKCC | HKEY_CURRENT_CONFIG |
Subnodes may take modifiers. The CLSID node takes the NoRemove identifier, which is a very good thing, as any entries made to HKCR\CLSID needs to keep all other subkeys intact. On the other hand, the {<GUID>} entry for CoHexagon is marked with ForceRemove, which tells ATL to remove any previous entries under this subkey and replace as necessary. Finally, although not present in your initial RGS file, a node may take the Delete modifier, which is used to remove (but not replace) a given key.
As we have seen in Chapter 3, a subkey may contain numerous values. In RGS syntax, named values are marked with the val modifier. RGS syntax allows you to enter string (S), numerical (D, for DWORD), or binary (B) values. For example, the version-independent ProgID lists two default string values:
ATLShapesServer.CoHexagon = s 'CoHexagon Class' { CLSID = s '{4A01DD03-066C-11D3-B8E5-0020781238D4}' CurVer = s 'ATLShapesServer.CoHexagon.1' }
The final point of interest is the %MODULE% tag. RGS syntax allows you to define placeholder types, which are dynamically added to the script during the registration/unregistra- tion process. %MODULE% is one such predefined placeholder, which resolves to a call to the Win32 GetModuleFileName() function. It is also possible to define your own custom placeholders, as we will see in Chapter 9.
That finishes up our examination of the ATL-generated files. As you can see, ATL has done quite an admirable job of providing component housing code, as well as basic COM riggings (a class factory, IUnknown implementation, and self-registration support). Of course, we still have quite a way to go before we understand exactly how ATL performs its magic. Rest assured we will dig into the gory details in upcoming chapters. But for now let's push onward and learn how to extend the wizard-generated code with interface methods and learn about interface "properties."
| < Free Open Study > |
|