Methods Required of an Object Map Class


The CAtlModule class registers, unregisters, initializes, and uninitializes noncreateable object map entries. In addition, it creates class objects and class instances for regular object map entries.

A class listed in the object map using the OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO macro must provide the first three well-known static methods listed in Table 5.1. A class listed in the object map that uses the OBJECT_ENTRY_AUTO macro must provide the same three methods as a noncreateable class, plus the additional well-known static methods listed in Table 5.1.

Table 5.1. Object Map Support Functions

Static Member Function

Description

UpdateRegistry

Registers and unregisters the class. The DECLARE_REGISTRY_RESOURCE macros provide various implementations of this method for nonattributed projects. Attributed projects have an implementation injected by the attribute provider.

ObjectMain

Initializes and uninitializes the class. CComObjectRootBase provides a default implementation of this method.

GetCategoryMap

Returns a pointer to a component category map. The BEGIN_CATEGORY_MAP macro provides an implementation. CComObjectRootBase provides a default implementation of this method.

CreatorClass::CreateInstance

_The DECLARE_AGGREGATABLE macros set the _CreatorClass typedef to the name of the class that creates instances. CComCoClass provides a default definition of this typedef.

_ClassFactoryCreatorClass:: CreateInstance

The DECLARE_CLASSFACTORY macros set the _ClassFactoryCreatorClass typedef to the name of the class that creates class objects. CComCoClass provides a default definition of this typedef.

GetObjectDescription

Returns the object description text string. CComCoClass provides a default implementation of this method.


All classes listed in the object map must define an UpdateRegistry method, which is the only method not provided by any base class. As you'll see soon, ATL contains various macros that expand to different implementations of this method, so the method is not difficult to provide.

All ATL objects derive from CComObjectRootBase, so they already have a default implementation of the ObjectMain method.

Most createable ATL classes also derive from CComCoClass, which provides the implementation of all four remaining methods that an object map entry requires, as well as both of the required typedefs. ATL also contains a set of macros that define and implement GetCategoryMap.

Class Registration Support Methods

When a server registers all the COM classes it contains, ATL iterates over the object map. The pfnUpdateRegistry field in the object map contains a pointer to the class's UpdateRegistry method. For each entry, ATL calls the UpdateRegistry method to register or unregister the class. Then, it registers or unregisters the component categories for the class using the table of required and implemented categories that the GetCategoryMap method provides.

The GetObjectDescription Method

The GetObjectDescription static method retrieves the text description for your class object. As shown previously, the default implementation returns NULL. You can override this method with the DECLARE_OBJECT_DESCRIPTION macro. For example:

class CMyClass : public CComCoClass< ... >, ... { public:    DECLARE_OBJECT_DESCRIPTION("MyClass Object Description")    ... }; 


The UpdateRegistry Method

Every class that you list in the object map, whether createable or noncreateable, must provide an UpdateRegistry static member function. The ATL server implementation calls this method to ask the class to register and unregister itself, depending on the value of bRegister.

static HRESULT WINAPI UpdateRegistry(BOOL bRegister) ; 


When you ask a COM server to register its classes, it registers all of them. You can't ask a server to register just one class or some subset of its classes. To register or unregister a subset of the classes in a server, you need to provide a Component Registrar object. This is a COM-createable class that implements the IComponent-Registrar interface. At one time, Microsoft Transaction Server was going to use the Component Registrar object to register and unregister individual classes in a server. However, I can find no references describing if, when, or how MTS/COM+ uses the Component Registrar object. Basically, as of this writing, the Component Registrar object and class object descriptions seem to be an unused feature, and you shouldn't use them.

The DECLARE_REGISTRY Macros

You can provide a custom implementation of UpdateRegistry (a.k.a. write it yourself) or use an ATL-provided implementation. ATL provides an implementation of this method when you use one of the following macros in your class declaration:

#define DECLARE_NO_REGISTRY()\                                           static HRESULT WINAPI UpdateRegistry(BOOL /*bRegister*/) \           {return S_OK;}                                                   #define DECLARE_REGISTRY(class, pid, vpid, nid, flags)\                  static HRESULT WINAPI UpdateRegistry(BOOL bRegister) {\                  return _Module.UpdateRegistryClass(GetObjectCLSID(), \                   pid, vpid, nid,\                                                     flags, bRegister);\                                          }                                                                #define DECLARE_REGISTRY_RESOURCE(x)\                                    static HRESULT WINAPI UpdateRegistry(BOOL bRegister) {\              ...                                                                  return ATL::_pAtlModule->UpdateRegistryFromResource(_T(#x), \            bRegister); \                                                    ...                                                                  }                                                                #define DECLARE_REGISTRY_RESOURCEID(x)\                                  static HRESULT WINAPI UpdateRegistry(BOOL bRegister) {\              ...                                                                  return ATL::_pAtlModule->UpdateRegistryFromResource(x, \                 bRegister); \                                                    ...                                                                  }                                                                


In most circumstances, you shouldn't use two of these Registry macros. The DECLARE_REGISTRY macro relies upon the old ATL 3 CComModule class to do its work. CComModule is no longer used as of ATL 7; if you try to use DECLARE_REGISTRY in an ATL 7 or 8 project, you'll see compile errors resulting from references to the deprecated CComModule class. Unless you're porting an ATL 3 project to ATL 7+, you should not use DECLARE_REGISTRY.

The second Registry macro to steer clear of is the DECLARE_NO_REGISTRY macro. This macro simply returns S_OK from the UpdateRegistry method, so no class information is entered in the Registry. The intent was that noncreateable classes can't be created, so you shouldn't put their CLSID information in the Registry. The problem is that any class that wants to throw COM exceptions still needs the CLSID-ProgId association in the Registry. ATL's support code for populating COM exception objects relies upon the ProgIdFromCLSID function, which fails for any class that uses DECLARE_NO_REGISTRY.

The most flexible registration technique for ATL servers is to use Registry scripts. When asked to register or unregister, a server using Registry scripts uses an interpreter object to parse the script and make the appropriate Registry changes. The interpreter object implements the IRegistrar interface. ATL provides such an object, which can be either statically linked to reduce dependencies or dynamically loaded for the smallest code size. The choice is made via the _ATL_STATIC_REGISTRY macro, which is discussed later in this chapter.

The DECLARE_REGISTRY_RESOURCE and DECLARE_REGISTRY_RESOURCEID macros provide an implementation of UpdateRegistry that delegates the call to the CAtlModule::UpdateRegistryFromResource method. You specify a string resource name when you use the first macro. The second macro expects an integer resource identifier. The UpdateRegistryFromResource runs the script contained in the specified resource. When bRegister is trUE, this method adds the script entries to the system Registry; otherwise, it removes the entries.

Registry Script Files

Registry scripts are text files that specify what Registry changes must be made for a given CLSID. Wizard-generated code uses an RGS extension by default for Registry script files. Your server contains the script file as a custom resource of type REGISTRY in your executable or DLL.

Registry script syntax isn't complicated; it can be summarized as follows:

[NoRemove | ForceRemove | val] Name [ = s | d | m | b 'Value'] {   ... optional script entries for subkeys } 


The NoRemove prefix specifies that the parser should not remove the key when unregistering. The ForceRemove prefix specifies that the parser should remove the current key and any subkeys before writing the key.[4] The val prefix specifies that the entry is a named value, not a key. The s and d value prefixes indicate REG_SZ and REG_DWORD, respectively. The m value prefix indicates a multistring (REG_MULTI_SZ), and the b value prefix denotes a binary value (REG_BINARY). The Name token is the string for the named value or key. It must be surrounded by apostrophes when the string contains spaces; otherwise, the apostrophes are optional. ATL's parser recognizes the standard Registry keysfor example, HKEY_CLASSES_ROOTas well as their four character abbreviations (HKCR).

[4] Be careful when writing Registry scripts by hand. For example, don't put the ForceRemove prefix on the node for HKEY_CLASSES_ROOT\CLSID. More than one developer machine has had to be repaved because a Registry script removed a lot more than was intended.

Here's a REGEDIT4 sample for the nontrivial class registration for a sample class named Demagogue. Watch out: A few lines are too long to list on the page and have wrapped.

REGEDIT4 [HKEY_CLASSES_ROOT\ATLInternals.Demagogue.1] @="Demagogue Class" [HKEY_CLASSES_ROOT\ATLInternals.Demagogue.1\CLSID] @="{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}" [HKEY_CLASSES_ROOT\ATLInternals.Demagogue] @="Demagogue Class" [HKEY_CLASSES_ROOT\ATLInternals.Demagogue\CLSID] @="{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}" [HKEY_CLASSES_ROOT\ATLInternals.Demagogue\CurVer] @="ATLInternals.Demagogue.1" [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}] @="Demagogue Class" [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\ProgID] @="ATLInternals.Demagogue.1" [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\VersionIndependentProgID] @="ATLInternals.Demagogue" [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\Programmable] [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\InprocServer32] @="C:\\ATLINT~1\\Debug\\ATLINT~1.DLL" "ThreadingModel"="Apartment" [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\TypeLib] @="{95CD3721-FC5C-11D1-8CC3-00A0C9C8E50D}" [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\Implemented Categories] [HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\Implemented Categories\{0D22FF22-28CC-11D2-ABDD-00A0C9C8E50D}] 


The corresponding Registry script looks like this:

HKCR {   ATLInternals.Demagogue.1 = s 'Demagogue Class'   {     CLSID = s '{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}'   }   ATLInternals.Demagogue = s 'Demagogue Class'   {     CLSID = s '{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}'     CurVer = s 'ATLInternals.Demagogue.1'   }   NoRemove CLSID   {     ForceRemove {95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D} = s 'Demagogue       Class'     {       ProgID = s 'ATLInternals.Demagogue.1'       VersionIndependentProgID = s 'ATLInternals.Demagogue'       ForceRemove 'Programmable'       InprocServer32 = s '%MODULE%'       {         val ThreadingModel = s 'Apartment'       }       'TypeLib' = s '{95CD3721-FC5C-11D1-8CC3-00A0C9C8E50D}'       'Implemented Categories'       {         {0D22FF22-28CC-11D2-ABDD-00A0C9C8E50D}       }     }   } } 


When you have the resource script file, you reference the file in your server's resource (.rc) file. You can reference it using either an integer identifier or a string identifier. In a typical ATL project, each class can have a Registry script file, and the server as a whole typically has its own unique Registry script file.

In the following examples, the Demagogue script file uses the integer identifier IDR_DEMAGOGUE. The EarPolitic script file uses EARPOLITIC as its string identifier. ATL wizard-created classes use the DECLARE_REGISTRY_RESOURCEID macro to specify a resource by its integer identifier. You can use the DECLARE_REGISTRY_RESOURCE macro to identify a resource by its name.

// resource.h file #define IDR_DEMAGOGUE                   102 // Server.rc file IDR_DEMAGOGUE           REGISTRY DISCARDABLE    "Demagogue.rgs" EARPOLITIC              REGISTRY DISCARDABLE    "EarPolitic.rgs" // Demagogue.h file class ATL_NO_VTABLE CDemagogue :     public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CDemagogue, &CLSID_Demagogue>, ... public: DECLARE_REGISTRY_RESOURCEID(IDR_DEMAGOGUE) ... }; // EarPolitic.h file class ATL_NO_VTABLE CEarPolitic :     public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CEarPolitic, &CLSID_EarPolitic>, ... { public: DECLARE_REGISTRY_RESOURCE(EARPOLITIC) ... }; 


Registry Script Variables

Note that in the Registry script, one of the lines references a symbol called %MODULE%.

InprocServer32 = s '%MODULE%' 


When the parser evaluates the script, it replaces all occurrences of the Registry script variable %MODULE% with the actual results of a call to GetModuleFileName. So what the parser actually registered looked like this:

InprocServer32 = s 'C:\ATLInternals\Debug\ATLInternals.dll' 


You can use additional, custom Registry script variables in your scripts. Your server must provide the Registry script parser with a table that maps variable names to replacement values when you ask the parser to parse the script. The parser substitutes the replacement values for the variable names before registration.

To use custom Registry script variables, first select the Registry script variable name, using percent signs to delimit the name. Here is a sample line from a Registry script:

DateInstalled = s '%INSTALLDATE%' 


You now have two choices. If your script variables are global, you can provide an override of the AddCommonRGSReplacements method in your derived module class. An example implementation looks like this:

HRESULT AddCommonRGSReplacements(IRegistrarBase *pRegistrar) {     BaseModule::AddCommonRGSReplacements( pRegistrar );     OLECHAR wszDate [16]; SYSTEMTIME st;     GetLocalTime(&st);     wsprintfW(wszDate, L"%.4d/%.2d/%.2d", st.wYear,         st.wMonth, st.wDay);     pRegistrar->AddReplacement( OLESTR("INSTALLDATE"), wszDate ); } 


You must call the base class's version of AddCommonRGSReplacements to make sure the APPID variable gets added as well.

If you don't want to have all of your replacements added in one place, you can define a custom UpdateRegistry method instead of using the DECLARE_REGISTRY_RESOURCEID macro in your class. In the method, build a table of replacement name-value pairs. Finally, call the CAtlModule::UpdateRegistryFromResource method, specifying the resource identifier, a register/unregister flag, and a pointer to the replacement name-value table. Note that ATL uses the provided table entries in addition to the default replacement map (which, as of this writing, contains only %MODULE% and %APPID%).

Here is an example from the Demagogue class that substitutes the variable %INSTALLDATE% with a string that contains the current date:

static HRESULT WINAPI UpdateRegistry(BOOL b) {     OLECHAR wszDate [16]; SYSTEMTIME st;     GetLocalTime(&st);     wsprintfW(wszDate, L"%.4d/%.2d/%.2d", st.wYear,         st.wMonth, st.wDay);     _ATL_REGMAP_ENTRY rm[] = {         { OLESTR("INSTALLDATE"), wszDate},         { 0, 0 } };     return _pAtlModule->UpdateRegistryFromResource(         IDR_DEMAGOGUE, b, rm); } 


After registration of the class, the Registry key DateInstalled contains the year, month, and day, in the form yyyy/mm/dd, at the time of install.

The GetCategoryMap Method

The last step in the registration process for each class in the object map is to register the component categories for the class. The ATL server registration code calls each class's GetCategoryMap method to ask the class for its list of required and implemented component categories. The method looks like this:

static const struct _ATL_CATMAP_ENTRY* GetCategoryMap() { return NULL; } 


The ATL server registration code uses the standard component category manager object (CLSID_StdComponentCategoriesMgr) to register your class's required and implemented categories. Older versions of Win32 operating systems do not have this component installed. When the category manager is not installed, your class's registration of its component categories silently fails. Typically, this is not good. However, Microsoft permits you to redistribute the standard component category manager (comcat.dll) with your application.

The Component Category Map

Typically, you use ATL-provided category map macros for the implementation of this method. Here's a typical category map:

// {0D22FF22-28CC-11d2-ABDD-00A0C9C8E50D} static const GUID CATID_ATLINTERNALS_SAMPLES = {0xd22ff22, 0x28cc, 0x11d2, {0xab, 0xdd, 0x0, 0xa0, 0xc9, 0xc8,      0xe5, 0xd}}; BEGIN_CATEGORY_MAP(CDemagogue) IMPLEMENTED_CATEGORY(CATID_ATLINTERNALS_SAMPLES) END_CATEGORY_MAP() 


This example defines a component category called CATID_ATLINTERNALS_SAMPLES. All examples in this book register themselves as a member of this category.

The Category Map Macros

The BEGIN_CATEGORY_MAP macro declares the GetCategoryMap static member function. This returns a pointer to an array of _ATL_CATMAP_ENTRY enTRies, each of which describes one component category that your class either requires or implements.

#define BEGIN_CATEGORY_MAP(x)\                                     static const struct ATL::_ATL_CATMAP_ENTRY* GetCategoryMap() {\    static const struct ATL::_ATL_CATMAP_ENTRY pMap[] = {           


The IMPLEMENTED_CATEGORY and REQUIRED_CATEGORY macros populate the table with the appropriate entries:

#define IMPLEMENTED_CATEGORY(catid) { \          _ATL_CATMAP_ENTRY_IMPLEMENTED, &catid }, #define REQUIRED_CATEGORY(catid) { \             _ATL_CATMAP_ENTRY_REQUIRED, &catid },    


The END_CATEGORY_MAP adds a delimiting entry to the table and completes the GetCategoryMap function:

#define END_CATEGORY_MAP()\               { _ATL_CATMAP_ENTRY_END, NULL } };\    return( pMap ); }                   


Each table entry contains a flag (indicating whether the entry describes a required category, an implemented category, and the end of table entry) and a pointer to the CATID.

struct _ATL_CATMAP_ENTRY {                 int iType;                              const CATID* pcatid;                 };                                      #define _ATL_CATMAP_ENTRY_END         0 #define _ATL_CATMAP_ENTRY_IMPLEMENTED 1 #define _ATL_CATMAP_ENTRY_REQUIRED    2 


The ATL helper function AtlRegisterClassCategoriesHelper iterates through the table and uses COM's standard component category manager to register each CATID as a required or implemented category for your class. The ATL server registration code uses this helper function to register the component categories for each class in the object map.

Unfortunately, the category map logic does not add a category to a system before trying to enroll a class as a member of the (nonexistent) category. If you want to add categories that don't already exist, you must enhance the registration of the server itself so that it registers any custom component categories your classes use. For example, the following Registry script registers the AppID for an inproc server and also adds a component category to the list of component categories on the system:

HKCR {     NoRemove AppID     {         {A11552A2-28DF-11d2-ABDD-00A0C9C8E50D} = s 'ATLInternals'         'ATLInternals.DLL'         {             val AppID = s {A11552A2-28DF-11d2-ABDD-00A0C9C8E50D}         }     }     NoRemove 'Component Categories'     {         {0D22FF22-28CC-11d2-ABDD-00A0C9C8E50D}         {             val 409 = s 'ATL Internals Example Components'         }     } } 


This technique defines all categories (just one in the previous example) that the classes implemented in this server in the System registry use. Separately, each class registers itself as a member of one or more of these categories.

Server, Not Class, Registration

Often, you need Registry entries for the server (inproc, local, or service) as a whole. For example, the HKCR/AppID Registry entries for a server apply to the entire DLL or EXE, and not to a specific class implemented in the DLL or EXE. As mentioned previously, you must register a new component category with the system before you can enroll a class as a member of the category. Until now, you've only seen support for registration of class-specific information.

This is simple enough. Write a Registry script that adds the entries the server requires, such as its AppID and component categories that the server's classes use. Then, register the server's script before registering all the classes implemented in the server. The wizard-generated code for all three server types does precisely this for you.

The ATL wizard-generated code for a server creates a Registry script file for your server registration in addition to any Registry script files for your classes. By default, the server Registry script defines only an AppID for the server. Here are the entries for an ATL project called MathServer:

// In the resource.h file #define IDR_MATHSERVER        100 // In the MathServer.rc file IDR_MATHSERVER REGISTRY "MathServer.rgs" // MathServer.rgs file HKCR {     NoRemove AppID {         '%APPID%' = s 'MathServer'         'MathServer.DLL'         {             val AppID = s '%APPID%'         }     } } 


The RGS files are similar for all three server typesinproc, local server, and Windows service. The mechanics used to invoke the registration code that executes the script differ among the three server types.

Inproc Server Registration

Inproc server registration for classes and for the server itself occurs in the call to the DllRegisterServer enTRy point. This method delegates the work to a specialization of CAtlModule that is used only for inproc servers; this class is called CAtlDllModuleT.

template <class T>                                             class ATL_NO_VTABLE CAtlDllModuleT : public CAtlModuleT<T> {   public:                                                          ...                                                            HRESULT DllRegisterServer(BOOL bRegTypeLib = TRUE) {             ...                                                            T* pT = static_cast<T*>(this);                                 HRESULT hr = pT->RegisterAppId(); // execute server script     ...                                                            return hr;                                                   }                                                              ...                                                          };                                                             


The RegisterAppId function ultimately calls the UpdateRegistryFromResource function through the UpdateRegistryAppId helper generated by the DECLARE_REGISTRY_APPID_RESOURCEID macro in our project.cpp file. This macro is discussed in more detail later in this chapter.

Local Server and Windows Service Registration

A local server specializes CAtlModule with a class called CAtlExeModuleT. A Windows service further extends CAtlExeModuleT with a derived class called CAtlServiceModuleT. Both classes supply WinMain code that registers the server's resource script entries and then calls the _AtlModule object's RegisterServer method to do the same for each class in the object map. These classes unregister each object in the server before running the server's unregistration script.

template <class T>                                                  class CAtlExeModuleT : public CAtlModuleT<T> {                      public :                                                              int WinMain(int nShowCmd) throw() {                                   ...                                                                 HRESULT hr = S_OK;                                                  LPTSTR lpCmdLine = GetCommandLine();                                if (pT->ParseCommandLine(lpCmdLine, &hr) == true)                     hr = pT->Run(nShowCmd);                                           ...                                                               }                                                                   bool ParseCommandLine(LPCTSTR lpCmdLine, HRESULT* pnRetCode) {        *pnRetCode = S_OK;                                                  TCHAR szTokens[] = _T("-/");                                        T* pT = static_cast<T*>(this);                                      LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);                 while (lpszToken != NULL) {                                           if (WordCmpI(lpszToken, _T("UnregServer"))==0) {                      *pnRetCode = pT->UnregisterServer(TRUE); // server script           if (SUCCEEDED(*pnRetCode))                                            *pnRetCode = pT->UnregisterAppId(); // all class scripts          return false;                                                   }                                                                   // Register as Local Server                                         if (WordCmpI(lpszToken, _T("RegServer"))==0) {                        *pnRetCode = pT->RegisterAppId(); // server script                  if (SUCCEEDED(*pnRetCode))                                            *pnRetCode = pT->RegisterServer(TRUE); // all class scripts       return false;                                                     }                                                                   lpszToken = FindOneOf(lpszToken, szTokens);                        }                                                                   return true;                                                       }                                                                 };                                                                  


Class Initialization and Uninitialization

The ObjectMain Method

In C++, constructors and destructors are used to provide instance-level initialization and cleanupthat is, code that runs each time an instance of a class is either created or destroyed. Often, it is useful to provide class-level initialization and cleanup codecode that runs only once per class, regardless of the number of class instances that are ultimately created. Some languages support such behavior directly through static constructors. Unfortunately, C++ is not one of those languages. So, ATL supports class-level initialization for all classes listed in the object map, createable and noncreateable, through the ObjectMain method. In fact, you'll frequently add a noncreateable class entry to the object map solely because the noncreateable class needs class-level initialization. (A createable class must always be in the object map and thus always receives class-level initialization.)

When an ATL server initializes, it iterates over the object map and calls the ObjectMain static member function (with the bStarting parameter set to true) of each class in the map. When an ATL server terminates, it calls the ObjectMain function (with the bStarting parameter set to false) of each class in the map. You always have a default implementation, provided by CComObjectRootBase, that does nothing and looks like this:

static void WINAPI ObjectMain(bool /* bStarting */ ) {}; 


When you have class initialization and termination logic (compared to instance initialization and termination logic), define an ObjectMain static member function in your class and place the logic there.[5]

[5] Instance initialization and termination logic should go in the FinalConstruct and FinalRelease methods, as Chapter 4, "Objects in ATL," describes.)

class ATL_NO_VTABLE CSoapBox :     public CComObjectRootEx<CComSingleThreadModel>,     ... { public:     DECLARE_NO_REGISTRY()     ...     static void WINAPI ObjectMain(bool bStarting) ; }; 


Instantiation Requests

Having class initialization logic run may be useful, but if clients can't create an instance, an initialized class is no help. Next, let's look at how ATL supports creating COM class factories to bring your COM objects to life.

Class Object Registration

ATL creates the class objects for an inproc server slightly differently than it does the class objects for a local server or service. For an inproc server, ATL defers creating each class object until the SCM actually requests the class object via DllGetClassObject. For a local server or service, ATL creates all class objects during server initialization and then registers the objects with the SCM.

For an inproc server, ATL uses the AtlComModuleGetClassObject helper function to scan the object map; create the class object, if necessary; and return the requested interface on the class object.

ATLINLINE ATLAPI AtlComModuleGetClassObject(                               _ATL_COM_MODULE* pComModule, REFCLSID rclsid, REFIID riid,          LPVOID* ppv) {                                                      ...                                                                 for (_ATL_OBJMAP_ENTRY** ppEntry =                                      pComModule->m_ppAutoObjMapFirst;                                    ppEntry < pComModule->m_ppAutoObjMapLast; ppEntry++) {              if (*ppEntry != NULL) {                                                _ATL_OBJMAP_ENTRY* pEntry = *ppEntry;                               if ((pEntry->pfnGetClassObject != NULL) &&                              InlineIsEqualGUID(rclsid, *pEntry->pclsid)) {                       if (pEntry->pCF == NULL) {                                                CComCritSecLock<CComCriticalSection>                                       lock(pComModule->m_csObjMap, false);                         hr = lock.Lock();                                                   ...                                                                 if (pEntry->pCF == NULL)                                                   hr = pEntry->pfnGetClassObject(                                     pEntry->pfnCreateInstance,                                          __uuidof(IUnknown),                                                 (LPVOID*)&pEntry->pCF);                                }                                                                  if (pEntry->pCF != NULL)                                                hr = pEntry->pCF->QueryInterface(riid,                                  ppv);                                                       break;                                                        }                                                               }                                                                }                                                                   if (*ppv == NULL && hr == S_OK)                                          hr = CLASS_E_CLASSNOTAVAILABLE;                                return hr;                                                      }                                                                   


Notice how the logic checks to see if the class object has not already been created (pEntry->pCF == NULL), acquires the critical section that guards access to the object map, and then checks once more that pCF is still NULL. What might not be obvious is why ATL checks twice. This is to maximize concurrency by avoiding grabbing the critical section in the normal case (when the class factory has already been created).

Also notice that ATL caches the IUnknown interface for the class object in the object map entry's pCF member variable. Because the SCM can make subsequent requests for the same class object, ATL caches the IUnknown pointer to the object in the pCF field of the object map entry for the class. Subsequent requests for the same class object reuse the cached interface pointer.

You can use the helper method, called GetClassObject, in your CAtlModuleT global object (discussed in detail later in the chapter) to retrieve a previously registered class object. However, this works only for inproc servers; the CAtlExeModuleT and CAtlServiceModuleT classes, used for local servers and services, respectively, don't include this member function.

// Obtain a Class Factory (DLL only)                 HRESULT GetClassObject(REFCLSID rclsid, REFIID riid,     LPVOID* ppv);                                    


You use it like this:

IClassFactory* pFactory; HRESULT hr = _pAtlModule->GetClassObject (  CLSID_Demagogue, IID_IClassFactory,  reinterpret_cast< void** >(&pFactory)); 


A local server must register all its class objects during the server's initialization. ATL uses the AtlComModuleRegisterClassObjects helper function to register all the class objects in a local server. This helper function iterates over the object map calling RegisterClassObject for each object map entry.

ATLINLINE ATLAPI AtlComModuleRegisterClassObjects(               _ATL_COM_MODULE* pComModule,                                   DWORD dwClsContext, DWORD dwFlags) {                           ...                                                            HRESULT hr = S_FALSE;                                          for (_ATL_OBJMAP_ENTRY** ppEntry =                               pComModule->m_ppAutoObjMapFirst;                               ppEntry < pComModule->m_ppAutoObjMapLast && SUCCEEDED(hr);     ppEntry++) {                                                   if (*ppEntry != NULL)                                            hr = (*ppEntry)->RegisterClassObject(dwClsContext,               dwFlags);                                                  }                                                             return hr;                                                  }                                                              


RegisterClassObject is a method of the _ATL_OBJMAP_ENTRY structure. It basically encapsulates the process of creating and then registering a class object from an object map entry. First, it ignores entries with a NULL pfnGetClassObject function pointer. This skips the noncreateable class entries in the map. Then, RegisterClassObject creates the instance of the class object and registers it:

struct _ATL_OBJMAP_ENTRY {                                 HRESULT WINAPI RegisterClassObject(DWORD dwClsContext,     DWORD dwFlags) {                                         IUnknown* p = NULL;                                      if (pfnGetClassObject == NULL) return S_OK;              HRESULT hRes = pfnGetClassObject(pfnCreateInstance,          __uuidof(IUnknown), (LPVOID*) &p);                   if (SUCCEEDED(hRes))                                         hRes = CoRegisterClassObject(*pclsid, p,                     dwClsContext, dwFlags, &dwRegister);             if (p != NULL) p->Release();                             return hRes;                                           }                                                      };                                                       


Notice that the function does not cache the IUnknown interface pointer to the class object that it creates: It hands the interface to the SCM (which caches the pointer), stores the registration code in the object map, and releases the interface pointer. Because ATL does not cache class object interface pointers in an out-of-process server, the simplest method to obtain your own class object from within the server is to ask the SCM for it by calling CoGetClassObject.

The _ClassFactoryCreatorClass and _CreatorClass Typedefs

When ATL created a class object in the previous examples, it asked your class to instantiate the class object by calling indirectly through the function pointer pfnGetClassObject, which ATL stores in the object map entry for the class.

struct _ATL_OBJMAP_ENTRY30 {                                        ...                                                               _ATL_CREATORFUNC* pfnGetClassObject; // Creates a class object    _ATL_CREATORFUNC* pfnCreateInstance; // Create a class instance   ...                                                             };                                                                


This member variable is of type _ATL_CREATORFUNC* and is a creator function.[6] Notice that the pfnCreateInstance member variable is also a creator function pointer.

[6] Although two types of creator functions logically exist (instance-creator functions and class-creator functions), all creator functions that the object map uses have a static member function with the example function signature. Chapter 4 discusses the various types of instance-creator functions in depth. This chapter discusses the various class-creator classes.

typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv); 


These function pointers are non-NULL only when you describe a COM-createable class using the OBJECT_ENTRY_AUTO macro.

#define OBJECT_ENTRY_AUTO(clsid, class) \                          __declspec(selectany) ATL::_ATL_OBJMAP_ENTRY \                 __objMap_##class = \                                           {&clsid, class::UpdateRegistry, \                              class::_ClassFactoryCreatorClass::CreateInstance, \            class::_CreatorClass::CreateInstance, NULL, 0, \               class::GetObjectDescription, class::GetCategoryMap, \          class::ObjectMain }; \                                         extern "C" __declspec(allocate("ATL$__m"))\                    __declspec(selectany) \                                        ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = \            &__objMap_##class; \                                           OBJECT_ENTRY_PRAGMA(class)                                 


A createable class must define a typedef called _ClassFactoryCreatorClass, which ATL uses as the name of the class object's creator class. The OBJECT_ENTRY_AUTO macro expects this creator class to have a static member function called CreateInstance, and it stores the address of this static member function in the pfnGetClassObject object map entry.

A createable class also must define a typedef called _CreatorClass, which ATL uses as the name of the class instance's creator class. The OBJECT_ENTRY_AUTO macro expects this creator class to have a static member function called CreateInstance, and it stores the address of this static member function in the pfnCreateInstance object map entry.

DECLARE_CLASSFACTORY

Typically, your createable class inherits a definition of the _ClassFactoryCreatorClass typedef from its CComCoClass base class. CComCoClass uses the DECLARE_CLASSFACTORY macro to define an appropriate default class object creator class, based on the type of server.

template <class T, const CLSID* pclsid = &CLSID_NULL>           class CComCoClass {                                             public:                                                             DECLARE_CLASSFACTORY()                                          DECLARE_AGGREGATABLE(T)                                         ...                                                         };                                                              


The _ClassFactoryCreatorClass Typedef

The DECLARE_CLASSFACTORY macro evaluates to the DECLARE_CLASSFACTORY_EX macro with CComClassFactory as the cf argument. The DECLARE_CLASSFACTORY_EX macro produces a typedef for the symbol _ClassFactoryCreatorClass. This typedef is the name of a creator class that ATL uses to create the class object for a class.

#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)                                                                     1             #if defined(_WINDLL) | defined(_USRDLL)                                         #define DECLARE_CLASSFACTORY_EX(cf) \                                             typedef ATL::CComCreator< \                                                          ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;                 #else                                                                           // don't let class factory refcount influence lock count                        #define DECLARE_CLASSFACTORY_EX(cf) \                                             typedef ATL::CComCreator< \                                                          ATL::CComObjectNoLock< cf > > _ClassFactoryCreatorClass;                 #endif                                                                          


When you build an inproc server, ATL standard build options define the _USRDLL preprocessor symbol. This causes the _ClassFactoryCreatorClass typedef to evaluate to a CComCreator class that creates a CComObjectCached<cf> version of your class object class cf. Out-of-process servers evaluate the typedef as a CComCreator class that creates a CComObjectNoLock<cf> version of the class object class cf.

Class Object Usage of CComObjectCached and CComObjectNoLock

As described in Chapter 4, "Objects in ATL," the CComObjectCached::AddRef method does not increment the server's lock count until the cached object's reference count changes from 1 to 2. Similarly, Release doesn't decrement the server's lock count until the cached object's reference count changes from 2 to 1.

ATL caches the IUnknown interface pointer to an inproc server's class object in the object map. This cached interface pointer represents a reference. If this cached reference affects the server's lock count, the DLL cannot unload until the server releases the interface pointer. However, the server doesn't release the interface pointer until the server is unloading. In other words, the server would never unload in this case. By waiting to adjust the server's reference count until there is a second reference to the class object, the reference in the object map isn't sufficient to keep the DLL loaded.

Also described in Chapter 4, CComObjectNoLock never adjusts the server's lock count. This means that an instance of CComObjectNoLock does not keep a server loaded. This is exactly what you need for a class object in a local server.

When ATL creates a class object for an out-of-process server, it registers the class object with the SCM; then ATL releases its reference. However, the SCM keeps an unknown number of references to the class object, where "unknown" means one or more. Therefore, the CComObjectCached class doesn't work correctly for an out-of-process class object. ATL uses the CComObjectNoLock class for out-of-process class objects because references to such objects don't affect the server's lifetime in any way. However, in modern versions of COM, the marshaling stub calls the class object's LockServer method when it marshals an interface pointer to a remote client (where "remote" is any other apartment or context). This keeps the server loaded when out-of-apartment clients have a reference to the class object.

Class Object Instantiation: CComCreator::CreateInstance Revisited

Earlier in this chapter, you saw the ATL class object registration helper functions AtlComModuleGetClassObject and RegisterClassObject. When they create a class object, they call the class object's creator class's CreateInstance method, like this:

// Inproc server class object instantiation                      ATLINLINE ATLAPI AtlComModuleGetClassObject(_ATL_COM_MODULE* pM,                                  REFCLSID rclsid,                                                 REFIID riid, LPVOID* ppv) {         ...                                                              hr = pEntry->pfnGetClassObject(pEntry->pfnCreateInstance,                                             __uuidof(IUnknown),                                              (LPVOID*)&pEntry->pCF);     ...                                                          };                                                               // Out of process server class object instantiation              struct _ATL_OBJMAP_ENTRY {                                         HRESULT WINAPI RegisterClassObject(DWORD dwClsContext,           DWORD dwFlags) {                                                   ...                                                              HRESULT hRes = pfnGetClassObject(pfnCreateInstance,                   _uuidof(IUnknown), (LPVOID*) &p);                           ...                                                            }                                                              };                                                               


Recall that the pfnGetClassObject member variable is set by the OBJECT_ENTRY_AUTO macro and contains a pointer to the CreateInstance method of _ClassFactoryCreatorClass:

class::_ClassFactoryCreatorClass::CreateInstance 


For an inproc server, this evaluates to the following (assuming that you're using the default class factory class, which is CComClassFactory):

class::ATL::CComCreator<                               ATL::CComObjectCached< ATL::CComClassFactory >     >::CreateInstance                              


For an out-of-process server, this evaluates to:

class::ATL::CComCreator<                               ATL::CComObjectNoLock< ATL::CComClassFactory >     >::CreateInstance                              


This means that the pfnGetClassObject member points to the CreateInstance method of the appropriate parameterized CComCreator class. When ATL calls this CreateInstance method, the creator class creates the appropriate type of the CComClassFactory instance (cached or no lock) for the server. You learned the definition of the CComCreator class in Chapter 4, "Objects in ATL," but let's examine part of the code in depth:

template <class T1>                                             class CComCreator {                                             public:                                                             static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,         LPVOID* ppv) {                                                  ...                                                             ATLTRY(p = new T1(pv))                                          if (p != NULL) {                                                    p->SetVoid(pv);                                                 ...                                                         }                                                               return hRes;                                                }                                                           };                                                              


After the creator class's CreateInstance method creates the appropriate class object, the method calls the class object's SetVoid method and passes it the pv parameter of the CreateInstance call.

Note that ATL uses a creator class to create both instances of class objects (sometimes called class factories) and instances of a class (often called COM objects). For regular COM objects, ATL defines the SetVoid method in CComObjectRootBase as a do-nothing method, so this creator class call to SetVoid has no effect on an instance of a class:

class CComObjectRootBase {     ...                        void SetVoid(void*) {} };                         


However, when a creator class creates an instance of a class factory, it typically creates a CComClassFactory instance. CComClassFactory overrides the SetVoid method. When a creator class calls the SetVoid method while creating a class factory instance, the method saves the pv parameter in its m_pfnCreateInstance member variable:

class CComClassFactory :                                                     public IClassFactory,                                                    public CComObjectRootEx<CComGlobalsThreadModel> {                    public:                                                                    ...                                                                      void SetVoid(void* pv) { m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;}   _ATL_CREATORFUNC* m_pfnCreateInstance;                                 };                                                                       


Let's look at the class objectcreation code in the ATL helper function again:

HRESULT hRes = pfnGetClassObject( pfnCreateInstance,    __uuidof(IUnknown), (LPVOID*) &p);                


The pfnGetClassObject variable points to the CreateInstance creator function that creates the appropriate instance of the class object for the server. The pfnCreateInstance variable points to the CreateInstance creator function that creates an instance of the class.

When ATL calls the pfnGetClassObject function, it passes the pfnCreateInstance object map entry member variable as the pv parameter to the CComClassFactory::CreateInstance method. The class object saves this pointer in its m_pfnCreateInstance member variable and calls the m_pfnCreateInstance function whenever a client requests the class object to create an instance of the class.

Whew! You must be wondering why ATL goes to all this trouble. Holding a function pointer to an instance-creation function increases the size of every class object by 4 bytes (the size of the m_pfnCreateInstance variable). Nearly everywhere else, ATL uses templates for this kind of feature. For example, we could define the CComClassFactory class to accept a template parameter that is the instance class to create. Then, each instance of the class object wouldn't need the extra 4-byte function pointer. The code would look something like this:

template <class T> class CComClassFactory : ... {   ...   STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,       void** ppvObj) {       ...       ATLTRY(p = new T ());       ...   } }; 


The problem with this alternative approach is that it actually takes more memory to implement. For example, let's assume that you have a server that implements three classes: A, B, and C.

On Win32, ATL's current approach takes 12 bytes per class object (4-byte vptr, 4-byte unused reference count, and 4-byte function pointer) times three class objects (A, B, and C), plus 20 bytes for a single CComClassFactory vtable (five entries of 4 bytes each). (All three classes objects are actually unique instances of the same CComClassFactory class, so all three share a single vtable. Each instance maintains a unique statea function pointerwhich creates different instance classes when called.) This is a total of 56 bytes for the three class objects. (Recall that ATL never creates more than one instance of a class object.)

The template approach takes 8 bytes per class object (4-byte vptr and 4-byte unused reference count) times three class objects (A, B, and C), plus 20 bytes each for three CComClassFactory vtables (one for CComClassFactory<A>, one for CComClassFactory<B>, and one for CComClassFactory<C>). In this case, the class object doesn't maintain state to tell it what instance class to create. Therefore, each class must have its own unique vtable that points to a unique CreateInstance method that calls new on the appropriate class. This is a total of 84 bytes.

This is mostly a theoretical calculation, though. Most heap managers round allocations up to a multiple of 16 bytes, which makes the instance sizes the same. This more or less makes moot the issue that class objects carry around a reference count member variable that they never use.

However, the memory savings are real, mainly because of the single required vtable. The function pointer implementation requires only a single copy of the IClassFactory methods and, therefore, only one vtable, regardless of the number of classes implemented by a server. The template approach requires one copy of the IClassFactory methods per class and, therefore, one vtable per class. The memory savings increase as you have more classes in a server.

I know, that's more detail than you wanted to know. Think of all the character and moral fiber you're building. That's why I'm here. Now, let's look at how CComClassFactory and related classes actually work.

CComClassFactory and Friends

DECLARE_CLASSFACTORY and CComClassFactory

Typically, ATL objects acquire a class factory by deriving from CComCoClass. This class includes the macro DECLARE_CLASSFACTORY, which declares CComClassFactory as the default class factory.

The CComClassFactory class is the most frequently used of the ATL-supplied class object implementations. It implements the IClassFactory interface and also explicitly specifies that the class object needs the same level of thread safety, CComGlobalsThreadModel, that globally available objects require. This is because multiple threads can access a class object when the server's threading model is apartment, free or both. Only when the server's threading model is single does the class object not need to be thread safe.

class CComClassFactory :                                          public IClassFactory,                                         public CComObjectRootEx<CComGlobalsThreadModel> {         public:                                                           BEGIN_COM_MAP(CComClassFactory)                                   COM_INTERFACE_ENTRY(IClassFactory)                        END_COM_MAP()                                               // IClassFactory                                              STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,     void** ppvObj);                                             STDMETHOD(LockServer)(BOOL fLock);                            // helper                                                     void SetVoid(void* pv);                                       _ATL_CREATORFUNC* m_pfnCreateInstance;                      };                                                            


Your class object must implement a CreateInstance method that creates an instance of your class. The CComClassFactory implementation of this method does some error checking and then calls the m_pfnCreateInstance function pointer to create the appropriate instance.

STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,           void** ppvObj) {                                                  ...                                                                   else hRes = m_pfnCreateInstance(pUnkOuter, riid, ppvObj);     }                                                                 return hRes;                                                  }                                                                 


As described earlier, the object map entry for your class contains the original value for this function pointer. It points to an instance-creator class, as described in Chapter 4, "Objects in ATL," and is set by the following part of the OBJECT_ENTRY_AUTO macro:

class::_CreatorClass::CreateInstance, NULL, 0, \ 


When your class derives from CComCoClass, you inherit a typedef for _CreatorClass, which is be used unless you override it with a new definition.

template <class T, const CLSID* pclsid = &CLSID_NULL> class CComCoClass {                                   public:                                                   DECLARE_AGGREGATABLE(T)                               ...                                               };                                                    


The default DECLARE_AGGREGATABLE macro defines a creator class, of type CComCreator2, that it uses to create instances of your class. This creator class creates instances using one of two other creator classes, depending on whether the instance is aggregated. It uses a CComCreator to create instances of CComObject<YourClass> when asked to create a nonaggregated instance; it creates instances of CComAggObject<YourClass> when you want your class to be aggre-gatable. When your class derives from CComCoClass, you inherit a typedef for _CreatorClass that is used unless you override it with a new definition.

#define DECLARE_AGGREGATABLE(x) public:\       typedef ATL::CComCreator2< \                   ATL::CComCreator< \                           ATL::CComObject< x > >, \                  ATL::CComCreator< \                            ATL::CComAggObject< x > > \         > _CreatorClass;                   


DECLARE_CLASSFACTORY_EX

You can specify a custom class factory class for ATL to use when creating instances of your object class. To override the default specification of CComClassFactory, add the DECLARE_CLASSFACTORY_EX macro to your object class and, as the macro parameter, specify the name of your custom class factory class. This class must derive from CComClassFactory and override the CreateInstance method. For example

class CMyClass : ..., public CComCoClass< ... > { public:    DECLARE_CLASSFACTORY_EX(CMyClassFactory)    ... }; class CMyClassFactory : public CComClassFactory {   ...   STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,        void** ppvObj); }; 


ATL also provides three other macros that declare a class factory:

  • DECLARE_CLASSFACTORY2. Uses CComClassFactory2 to control creation through a license.

    #define DECLARE_CLASSFACTORY2(lic) \                    DECLARE_CLASSFACTORY_EX(CComClassFactory2<lic>) 

  • DECLARE_CLASSFACTORY_SINGLETON. Uses CComClassFactorySingleton to construct a single CComObjectCached object and return the object in response to all instantiation requests.

    #define DECLARE_CLASSFACTORY_SINGLETON(obj) \                   DECLARE_CLASSFACTORY_EX(CComClassFactorySingleton<obj>) 

  • DECLARE_CLASSFACTORY_AUTO_THREAD. Uses CComClassFactoryAutoThread to create new instances in a round-robin manner in multiple apartments.

    #define DECLARE_CLASSFACTORY_AUTO_THREAD() \                DECLARE_CLASSFACTORY_EX(CComClassFactoryAutoThread) 

DECLARE_CLASSFACTORY2 and CComClassFactory2<lic>

The DECLARE_CLASSFACTORY2 macro defines CComClassFactory2 as your object's class factory implementation. CComClassFactory2 implements the IClassFactory2 interface, which controls object instantiation using a license. A CComClassFactory2 class object running on a licensed system can provide a runtime license key that a client can save. Later, when the client runs on a nonlicensed system, it can use the class object only by providing the previously saved license key.

template <class license>                                      class CComClassFactory2 :                                         public IClassFactory2,                                        public CComObjectRootEx<CComGlobalsThreadModel>,              public license {                                          public:                                                           ...                                                         STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,     void** ppvObj) {                                              ...                                                           if (!IsLicenseValid()) return CLASS_E_NOTLICENSED;            ...                                                               return m_pfnCreateInstance(pUnkOuter, riid, ppvObj);    }                                                             STDMETHOD(CreateInstanceLic)(IUnknown* pUnkOuter,               IUnknown* pUnkReserved, REFIID riid, BSTR bstrKey,            void** ppvObject);                                          STDMETHOD(RequestLicKey)(DWORD dwReserved, BSTR* pbstrKey);   STDMETHOD(GetLicInfo)(LICINFO* pLicInfo);                    ...                                                          };                                                            


Note that the main difference between CComClassFactory and CComClassFactory2 is that the latter class's CreateInstance method creates the instance only on a licensed systemthat is, IsLicenseValid returns trUE. The additional CreateInstanceLic method always creates an instance on a licensed system but creates an instance on an unlicensed system only when the caller provides the correct license key.

The template parameter to CComClassFactory2<license> must implement the following static functions:

VerifyLicenseKey

Returns trUE if the argument is a valid license key.

GetLicenseKey

Returns a license key as a BSTR.

IsLicenseValid

Returns trUE if the current system is licensed.


The following is an example of a simple license class:

const OLECHAR rlk[] = OLESTR("Some run-time license key") ; class CMyLicense                                            {                                                                protected:                                                         static BOOL VerifyLicenseKey(BSTR bstr) {                          return wcscmp (bstr, rlk) == 0;                                }                                                                static BOOL GetLicenseKey(DWORD dwReserved, BSTR* pBstr) {         *pBstr = SysAllocString(rlk);                                    return TRUE;                                                   }                                                                static BOOL IsLicenseValid() {                                     // Validate that the current system is licensed...               // May check for the presence of a specific license file, or     // may check for a particular hardware device                    if (...) return TRUE;                                          }                                                              };                                                               


You specify this license class as the parameter to the DECLARE_CLASSFACTORY2 macro in your object class. It overrides the _ClassFactoryCreatorClass typedef inherited from CComCoClass.

class ATL_NO_VTABLE CEarPolitic :     public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CEarPolitic, &CLSID_EarPolitic>,     ... { public:     DECLARE_CLASSFACTORY2 (CMyLicense)     ... }; 


The client code required for creating instances of this licensed class depends upon whether the client machine is licensed or is an unlicensed machine that instead provides a runtime license key. Typically, development is performed on a licensed machine where CoCreateInstance is used as usual. Then a call to RequestLicenseKey is used on the licensed machine to obtain a runtime license key that is persisted to file (or some other medium). CComClassFactory2 invokes IsLicenseValid to ensure that the calling code is running on a licensed machine before it hands out a runtime license key via RequestLicenseKey. Clients on nonlicensed machines then use this runtime license key to create instances using IClassFactory2::CreateInstanceLic.

Here's what the client code might look like for creating an instance of our licensed CEarPolitic class and obtaining a runtime license key:

// Obtain pointer to IClassFactory2 interface CComPtr<IClassFactory2> pcf; ::CoGetClassObject(__uuidof(CEarPolitic), CLSCTX_ALL, NULL,     __uuidof(pcf), (void**)&pcf); // Request a run-time license key  // only succeeds on licensed machine CComBSTR bstrKey; pcf->RequestLicKey(NULL, &bstrKey); // Save license key to a file for distribution // to run-time clients FILE* f = fopen("license.txt", "w+"); fwrite(bstrKey, sizeof(wchar_t), bstrKey.Length(), f); fclose(f); 


A user operating on a nonlicensed machine with a runtime license uses that license to create instances, like this:

// Obtain a pointer to the IClassFactory2 interface CComPtr<IClassFactory2> pcf; ::CoGetClassObject(__uuidof(CEarPolitic), CLSCTX_ALL, NULL,     __uuidof(pcf), (void**)&pcf); // Read in the run-time license key from disk WCHAR szKey[1025]; FILE* f = fopen("license.txt", "r"); int n = fread((void**)&szKey, sizeof(wchar_t), 1024, f); szKey[n] = '\0'; fclose(f); // Create an instance of the licensed object w/ the license key CComBSTR bstrKey(szKey); CComPtr<IEarPolitic> p; hr = pcf->CreateInstanceLic(NULL, NULL, __uuidof(p), bstrKey,     (void**)&p); 


DECLARE_CLASSFACTORY_SINGLETON and CComClassFactorySingleton

The DECLARE_CLASSFACTORY_SINGLETON macro defines CComClassFactorySingleton as your object's class factory implementation. This class factory creates only a single instance of your class. All instantiation requests return the requested interface pointer on this one (singleton) instance.

The template parameter specifies the class of the singleton. The class factory creates this singleton object as a member variable, m_spObj, of the class factory.

template <class T>                                            class CComClassFactorySingleton : public CComClassFactory {   public:                                                         // IClassFactory                                              STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,     void** ppvObj) {                                              ...                                                           hRes = m_spObj->QueryInterface(riid, ppvObj);                 ...                                                           return hRes;                                                }                                                             CComPtr<IUnknown> m_spObj;                                  };                                                            


The following is an example of a simple singleton class:

class CMyClass : ..., public CComCoClass< ... > { public:    DECLARE_CLASSFACTORY_SINGLETON(CMyClass) ... }; 


Singletons Are EvilOr, at the Very Least, Leaning Toward the Dark Side

You should avoid using singletons, if possible. A singleton in a DLL is unique only per process. A singleton in an out-of-process server is unique only per system, at bestand often not even then because of security and multiuse settings. Typically, most uses of a singleton are better modeled as multiple instances that share state instead of one shared instance.

In the current ATL implementation, the singleton class object does not marshal the interface pointer it produces to the calling apartment. This has subtle and potentially disastrous consequences. For example, imagine that you create an inproc singleton that resides in an STA. Every client from a different STA that "creates" the singleton receives a direct pointer to the singleton object, not to a proxy to the object, as you might expect. This has the following implications:

  • An inproc singleton can be concurrently accessed by multiple threads, regardless of the apartment in which it lives.

  • Therefore, the singleton must protect its state against such concurrent access.

  • In addition, the singleton must be apartment neutral because, conceptually, it lives in all apartments.

  • Therefore, an inproc singleton must not hold apartment-specific state, such as interface pointers, but instead should keep such pointers in the Global Interface Table (GIT).

  • Finally, a singleton cannot be aggregated.

A preferable approach to using singletons is to use nonsingleton "accessor" objects to access some piece of shared state. These accessor objects operate on the shared state through a lock or synchronization primitive so that the data is protected from concurrent access by multiple instances of the accessor objects. In the next example, the shared state is modeled as a simple static variable that stores a bank account object. Instances of the CTeller class access the bank account only after successfully acquiring the account lock.

class CAccount { public:     void Audit() { ... }     void Open() { ... }     void Close() { ... }     double Deposit(double dAmount) { ... }     double Withdraw(double dAmount) { ... } }; static CAccount s_account;             // shared state static CComAutoCriticalSection s_lock; // lock for serializing                                         // account access class CTeller { public:     void Deposit(double dAmount) {         s_lock.Lock();         s_account.Deposit(dAmount);         s_lock.Unlock();     } }; 


This technique of factoring out the shared state of the object still provides the semantics that most people seek when they turn to singletons, but it avoids the previous problems associated with singletons because the CTeller object itself is not a singleton. Each client that wants to manipulate the CAccount shared state creates a unique instance of the CTeller class. Consequently, a CTeller COM object can live in any apartment, can hold interface pointers, and need prevent only simultaneous access to the CAccount shared state instance.

DECLARE_CLASSFACTORY_AUTO_THREAD and CComClassFactoryAutoThread

The DECLARE_CLASSFACTORY_AUTO_THREAD macro defines CComClassFactoryAutoThread as your object's class factory implementation. This class factory creates each instance in one of a number of apartments. You can use this class only in an out-of-process server. Essentially, this class factory passes every instantiation request to your server's global instance of CAtlAutoThreadModuleT (discussed shortly), which does all the real work:

class CComClassFactoryAutoThread :                                ...                                                             STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid,     void** ppvObj) {                                                ...                                                             hRes = _pAtlAutoThreadModule.CreateInstance(                        m_pfnCreateInstance, riid, ppvObj);                         ...                                                           }                                                                 ...                                                           _ATL_CREATORFUNC* m_pfnCreateInstance;                        };                                                              


Whenever a server contains any classes using the DECLARE_CLASSFACTORY_AUTO_THREAD macro, the server's project.cpp file must declare a global instance of CAtlAutoThreadModule. (The name of the global variable used is immaterial.) The _pAtlAutoThreadModule variable shown earlier in the implementation of CreateInstance points to the global instance of CAtlAutoThreadModule that you declare in your project.cpp file. CAtlAutoThreadModule implements a pool of single-thread apartments (STAs) in an out-of-process server. By default, the server creates four STAs per processor on the system. The class factory forwards each instantiation request, in a round-robin order, to one of the STAs. This allocates the class instances in multiple apartments, which, in certain situations, can provide greater concurrent execution without the complexity of writing a thread-safe object.

The following is an example of a class that uses this class factory:

class CMyClass : ..., public CComCoClass< ... > { public:    DECLARE_CLASSFACTORY_AUTO_THREAD()    ... }; 


CAtlAutoThreadModuleT implements the IAtlAutoThreadModule interface, which ATL uses to create instances within CComClassFactoryAutoThread. You can name the global variable anything you want,[7] so the following declaration works just fine.

[7] However, names with leading underscores are reserved to the compiler and library vendor, so you should avoid using them so you don't run into conflicts with your libraries.

// project.cpp CAtlAutoThreadModule g_AtlAutoModule;    // any name will do 


ATL declares a global variable of type IAtlAutoThreadModule in atlbase.h.

__declspec(selectany)                        IAtlAutoThreadModule* _pAtlAutoThreadModule; 


This global variable is set in the constructor of CAtlAutoThreadModuleT, which is the template base class for CAtlAutoThreadModule:

template <class T,                                         class ThreadAllocator = CComSimpleThreadAllocator,     DWORD dwWait = INFINITE>                           class ATL_NO_VTABLE CAtlAutoThreadModuleT                  : public IAtlAutoThreadModule {                    public:                                                    CAtlAutoThreadModuleT(                                     int nThreads = T::GetDefaultThreads()) {               // only one per server                                 ATLASSERT(_pAtlAutoThreadModule == NULL);              _pAtlAutoThreadModule = this;                          m_pApartments = NULL;                                  m_nThreads= 0;                                     }                                                      ...                                                };                                                     


CComClassFactoryAutoThread checks that this _pAtlAutoThreadModule global is non-NULL before delegating instantiation requests to it. Debug builds issues an assertion indicating that you have not declared an instance of CAtlAutoThreadModule. Release builds simply return E_FAIL from the CreateInstance call.

CAtlAutoThreadModule uses CComApartment to manage an apartment for each thread in the module. The template parameter defaults to CComSimpleThreadAllocator, which manages thread selection for CComAutoThreadModule.

class CComSimpleThreadAllocator {                           public:                                                          CComSimpleThreadAllocator() { m_nThread = 0; }              int GetThread(CComApartment* /*pApt*/, int nThreads) {          if (++m_nThread == nThreads) m_nThread = 0;                 return m_nThread;                                       }                                                           int m_nThread;                                         };                                                          


CComSimpleThreadAllocator provides one method, GetThread, which selects the thread on which CAtlAutoThreadModuleT creates the next object instance. You can write your own apartment selection algorithm by creating a thread allocator class and specify it as the parameter to CAtlAutoThreadModuleT.

For example, the following code selects the thread (apartment) for the next object instantiation randomly (though it has the downside that I'm using a C runtime library function to get the random number).

class CRandomThreadAllocator { public:    int GetThread(CComApartment* /*pApt*/, int nThreads) {        return rand () % nThreads;    } }; 


Instead of selecting the default thread allocator by using CAtlAutoThreadModule, you create a new class that derives from CAtlAutoThreadModuleT and specify your new thread-selection class as the template parameter.

// project.cpp class CRandomThreadModule : public CAtlAutoThreadModuleT< CRandomThreadModule,    CRandomThreadAllocator> { ... }; CRandomThreadModule g_RandomThreadModule; // name doesn't matter 


Finally! You've seen the object map, which is a fundamental data structure in an ATL server. You've seen the various requirements and options for classes, both createable and noncreateable, for the classes to be listed in the object map. Now let's look at the part of ATL that actually uses the object map: the CAtlModule class, its derived classes, and the global variable of that type called _AtlModule.




ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net