Chapter 5. COM Servers
A Review of COM Servers
After you create one or more COM classes, you need to package the classes and install them on a system. The package is called a COM Server, which is a dynamically activated collection of the implementations of one or more COM classes. Modern Win32 versions of COM enable you to create a COM server as an in-process (inproc) server (a dynamic link library), an out-of-process server (an executable), or, on the Windows NT/2000/XP/2003 operating system, a system service executable. [1]
A COM server has three jobs, in addition to hosting the implementations of its classes:
Register and unregister all classes in the server and, potentially, the server itself.
Provide the COM Service Control Manager (SCM) access to all COM class objects implemented by the server (often called exposing the class objects). Class objects are frequently called class factory objects because they generally implement the
IClassFactory
interface.Manage the server’s lifetime. This typically means allowing the SCM to unload an inproc server from memory when it’s no longer used. A local server often terminates when there are no more references to objects managed by the server.
Technically, the first and third items are optional, but all COM servers should implement this functionality. Exactly how a server does this depends on the type of server.
Inproc Servers
An inproc server is a dynamic link library (DLL) that contains five well-known entry points, one for the Win32 operating systems and the other four for COM:
1BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,
2 LPVOID lpReserved);
3
4STDAPI DllRegisterServer(void);
5STDAPI DllUnregisterServer(void);
6STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
7 LPVOID* ppv);
8STDAPI DllCanUnloadNow(void);
Each of the Win32 operating systems calls a
DLL’s DllMain
function when it loads a DLL into a process
and removes a DLL from a process. The operating system also calls DllMain
each time the current process creates a new thread
and when a thread terminates cleanly. This function is optional but
present in all ATL inproc servers.
The DllRegisterServer
and DllUnregisterServer
functions create and remove,
respectively, in the Windows Registry all entries necessary for the
correct operation of the server and its classes. When you use the
REGSVR32 utility to register or unregister an inproc server, the
utility loads the DLL and calls the appropriate one of these
function in the DLL. Technically, both of these functions are
optional, but, in practice, you want to have them in your
server.
The COM SCM calls a server’s DllGetClassObject
function when it requires a class object
exposed by the server. The server should return the requested
interface, riid
, on the specified class object, rclsid
.
When you call the CoFreeUnusedLibraries
API, COM asks each inproc server in your process whether COM should
unload the DLL by calling the DllCanUnloadNow
function. An S_OK
return value means that the server permits COM to
unload the server. An S_FALSE
return value indicates that
the server is busy and COM cannot unload it.
Local Servers and Service-Based Servers
A local server or Windows service is an executable image (EXE) server that contains one well-known entry point:
1extern "C"
2int WINAPI _tWinMain(HINSTANCE hInstance,
3 HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd);
Executables cannot provide multiple specialized entry points as DLLs can, so a local server must use a different technique to implement the three requirements of a COM server: registration, exposed class objects, and lifetime management.
A local server registers and unregisters itself,
and then immediately terminates when the server parses its command
line and finds the well-known (noncase-sensitive) command-line
switches Regserver
and UnregServer
,
respectively.
A local server exposes its class objects by
calling the CoRegisterClassObject
API and handing an IUnknown
interface pointer to each of the server’s class
objects to the SCM. A local server must call this API after the SCM
starts the server process. [2] When the server is ready to
shut down, it must first call CoRevokeClassObject
to
notify the SCM that each of the server’s class objects is no longer
available for use.
As of this writing, a local server must register its class objects within 120 seconds. The time was shorter in previous software releases. It might change again in the future, so simply register your class objects as quickly as possible.
A local server manages its own lifetime. When a server detects that there are no references to any of the objects managed by the server, the server can shut down – or not, as desired. As you’ll see later in this chapter, detecting that there are no references is a little trickier than you might think.
COM Createable and Noncreateable Classes
A COM createable
class is a COM object class that supports using the CoCreateInstance
API to create instances. This implies
that the class must provide a class object and that the class
object implements the IClassFactory
interface.
A noncreateable class typically provides no
class object, so calling CoCreateInstance
using the
class’s CLSID fails. In many designs, far more noncreateable
classes exist than createable ones. For example, one version of
Microsoft Excel had approximately 130 classes, only 3 of which were
createable. You typically access interfaces on noncreateable
classes through properties or methods on createable classes or on
classes that have already been created by other means.
The Object Map and the CAtlModule Class
ATL uses two constructs to support the
functionality all types of servers require: the object map and one of three CAtlModule
-derived classes. As you’ll see shortly, the
behavior of a COM server differs considerably depending upon
whether that server is an inproc server, a local server, or a
Windows service. ATL has factored server functionality into CAtlDllModuleT
, CAtlExeModuleT
, and CAtlServiceModuleT
, each of which
extends the CAtlModule
base class. The discussion that
follows refers to CAtlModule
directly, but you should
realize that one of the derived classes actually does most of the
work. The object map (more properly entitled a class map) is a
table of all classes implemented in the server.
Various methods of the CAtlModule
class
use the object map to do the following:
Find each class and ask it to register and unregister itself
Create the class object, if any, for each createable class
Register and unregister the class objects with the SCM
Find a table of implemented and required component categories for a class
Call the static initialization and termination member functions for each class
The basic idea is that you describe the classes
that you are implementing in a server using the object map.
Whenever you need basic server functionality, there is probably a
method of the CAtlModule
class that implements much, if
not all, of the required functionality.
Many methods in the CAtlModule
class
iterate over the entries in the object map and either ask each
class to perform the required function or ask each class to provide
the information needed to allow the method to do it.
The Object Map
ATL manages the object map by allocating a
custom data segment within the PE file itself. Each coclass adds an
entry to the object map by allocating the _ATL_OBJMAP_ENTRY
structure (discussed in detail shortly)
within that same data segment. This produces a series of _ATL_OBJMAP_ENTRY
structures that are contiguous in memory
and, thus, can easily be iterated over by CAtlModule
when
it needs to perform registration, class object creation, and other
class-management services. Each class inserts an item into the
object map via the OBJECT_ENTRY_AUTO
macro declared in the
class header file outside the class declaration itself, as in the
following:
1class CMyClass : public CComCoClass< ... >, ...
2{
3public:
4
5
6 ...
7};
8
9OBJECT_ENTRY_AUTO(__uuidof(MyClass), CMyClass)
Here the coclass name
declared in the IDL file is MyClass
, so the __uuidof
keyword returns the CLSID.
Readers familiar with previous versions of ATL
will recall that the object map was implemented as a global C++
object declared in the server’s .cpp
file with macros such
as BEGIN_OBJECT_MAP
and END_OBJECT_MAP
. This
required each class to add information (namely, an OBJECT_ENTRY
macro) to a separate header file not
associated with the class, which complicates maintenance and causes
every class in the server to be compiled twice. The nice thing
about the new approach is that the file in which the OBJECT_ENTRY_AUTO
macro appears doesn’t matter; all
classes that declare one contribute entries that are contiguous in
memory. This means that information about each class can be located
next to the class definition, where it logically belongs. The
discussion of the OBJECT_ENTRY_AUTO
macro later in this
chapter reveals how ATL accomplishes this nifty trick.
The Object Map Macros
_ATL_OBJMAP_ENTRY Structure
The object map is an array of _ATL_OBJMAP_ENTRY
structures that look like this:
1struct _ATL_OBJMAP_ENTRY30 {
2 const CLSID* pclsid;
3 HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
4 _ATL_CREATORFUNC* pfnGetClassObject;
5 _ATL_CREATORFUNC* pfnCreateInstance;
6 IUnknown* pCF;
7 DWORD dwRegister;
8 _ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
9 _ATL_CATMAPFUNC* pfnGetCategoryMap;
10 void (WINAPI *pfnObjectMain)(bool bStarting);
11};
12
13
14typedef _ATL_OBJMAP_ENTRY30 _ATL_OBJMAP_ENTRY;
The structure contains the following fields, many of which are pointers to functions:
Field |
Description |
---|---|
|
Pointer to CLSID for this class entry |
|
The function that registers and unregisters the class |
|
The Creator function that creates an instance of the class object |
|
The Creator function that creates an instance of the class |
|
Pointer to the class object instance – |
|
Registration cookie |
|
The function that returns the object description for the class |
|
The function that returns the component category map |
|
The class initialization/termination function |
OBJECT_ENTRY_AUTO Macro
You use the OBJECT_ENTRY_AUTO
macro to
specify a COM-createable class. Typically, this means the specified
class derives from the CComCoClass
base class. Often these
are top-level objects in an object model. Clients typically create
such top-level objects using CoCreateInstance
.
1#define OBJECT_ENTRY_AUTO(clsid, class) \
2 __declspec(selectany) ATL::_ATL_OBJMAP_ENTRY \
3 __objMap_##class = \
4 {&clsid, class::UpdateRegistry, \
5 class::_ClassFactoryCreatorClass::CreateInstance, \
6 class::_CreatorClass::CreateInstance, NULL, 0, \
7 class::GetObjectDescription, class::GetCategoryMap, \
8 class::ObjectMain }; \
9 extern "C" __declspec(allocate("ATL$__m"))\
10 __declspec(selectany) \
11 ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = \
12 &__objMap_##class; \
13 OBJECT_ENTRY_PRAGMA(class)
A key part of the previous OBJECT_ENTRY_AUTO
macro definition is the __declspec(allocate)
modifier used to allocate an item of
type _ATL_OBJMAP_ENTRY
in a data segment named ATL$__m
. All classes in the server use this same macro (or
the NON_CREATEABLE
version discussed shortly), so they all
add items to the same contiguous ATL$__m
data segment,
which lays out the _ATL_OBJMAP_ENTRY
structures
contiguously in memory. The OBJECT_ENTRY_PRAGMA
is the bit
that actually forces the linker to include a symbol pointing to the _ATL_OBJMAP_ENTRY
. OBJECT_ENTRY_PRAGMA
is defined
as follows:
1#if defined(_M_IX86)
2#define OBJECT_ENTRY_PRAGMA(class)
3 __pragma(comment(linker,"/include:___pobjMap_" #class));
4...
5#endif
The pragma
shown in the macro
definition has the same effect as passing the /include
option to the command line of the linker. You might have noticed
the __declspec(selectany)
attribute adorning the __objMap_##class
and __pobj-Map_##class
variables
in the OBJECT_ENTRY_AUTO
macro. This construct appears in
many places in ATL, and it’s convenient because it instructs the
linker to ignore multiple definitions of a global data item that it
finds in a single object file. For instance, if more than one .cpp
file included a class definition that contained the OBJECT_ENTRY_AUTO
macro, both expansions of the __objMap_##class
and __pobj-Map_##class
globals
would be defined multiple times and would produce linker
errors.
Having each _ATL_OBJMAP_ENTRY
from
every class aligned contiguously in memory is part of what’s needed
to build an object map that CAtlModule
can iterate over.
The remaining parts are pointers that mark where in memory the map
starts and where it ends. ATL is clever here as well. It creates
two additional data segments named ATL$__a
and ATL$__z
and allocates a single NULL
_ATL_OBJMAP_ENTRY
pointer within each. The linker then
arranges the three data segments alphabetically so that the ATL$__m
segment containing all the server’s _ATL_OBJMAP_ENTRY
structures is sandwiched between two NULL
s. The code in atlbase.h
that does this is
shown here:
1#pragma section("ATL$__a", read, shared)
2#pragma section("ATL$__z", read, shared)
3#pragma section("ATL$__m", read, shared)
4extern "C" {
5__declspec(selectany) __declspec(allocate("ATL$__a"))
6 _ATL_OBJMAP_ENTRY* __pobjMapEntryFirst = NULL;
7__declspec(selectany) __declspec(allocate("ATL$__z"))
8 _ATL_OBJMAP_ENTRY* __pobjMapEntryLast = NULL;
9}
10
11#if !defined(_M_IA64)
12#pragma comment(linker, "/merge:ATL=.rdata")
13#endif
The alphabetical order of sections in the
resulting file is guaranteed through a special naming rule enforced
by the linker. If a section name has a $
character in it,
the linker goes through some odd but, in this case, useful
gyrations. First, it arranges the segments in order alphabetically.
Then, it strips the $
character and all characters after
the $
in the section name and merges the sections. This
way, you’re guaranteed that the data in the ATL$__a
segment comes first, then the ATL$__m
segment comes next,
and finally the ATL$__z
segment. The actual linked PE
format file then would have a single ATL segment instead of three
separate ones. [3]
This
also explains why the ATL team used ATL$__a
,
ATL$__m
, and ATL$__z
. It gives them future
flexibility to add sections and make sure they show up in the right
places.
The final step is the /merge
switch
added by the final #pragma
directive. This causes
everything in the ATL segment to be merged into the standard .rdata
section (which explains why you won’t find the ATL
section if you go looking with dumpbin
).
Take a look at the following three class definitions:
1class CDog : public CComCoClass< ... >, ...
2{ };
3OBJECT_ENTRY_AUTO(__uuidof(Dog), CDog)
4
5class CCat: public CComCoClass< ... >, ...
6{ };
7OBJECT_ENTRY_AUTO(__uuidof(Cat), CCat)
8
9class CMouse: public CComCoClass< ... >, ...
10{ };
11OBJECT_ENTRY_AUTO(__uuidof(Mouse), CMouse)
The actual layout of the object map in memory looks something like Figure5.1.
Figure 5.1. The ATL object map

The beginning of the map is
marked by _pobjMapEntryFirst
and its value is set to NULL
. It’s not possible to set _pobjMapEntryFirst
to one of the first “real” entries in the map because each is a
global object, and the order of global object construction in C++
is not guaranteed. As you’ll see later when we peer into the CAtlModule
-derived classes, all these classes must do to
walk the object map is start at __pobjMapEntryFirst
and
increment a pointer until that pointer is NULL
.
OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO macro
You use the OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO
macro to specify a
class that does not have an associated class object. Often these
are non-top-level objects in an object model. Clients typically
must call a method on a higher-level object in the object hierarchy
to obtain an instance of this class. Because the specified class
does not have an associated class object, clients cannot create an
instance by calling CoCreateInstance
.
1#defineOBJECT_ENTRY_NON_CREATEABLE_EX_AUTO(clsid, class) \
2 __declspec(selectany) ATL::_ATL_OBJMAP_ENTRY \
3 __objMap_##class = \
4 {&clsid, class::UpdateRegistry, NULL, NULL, NULL, 0, NULL, \
5 class::GetCategoryMap, class::ObjectMain }; \
6 extern "C" __declspec(allocate("ATL$__m" )) \
7 __declspec(selectany) \
8 ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = \
9 &__objMap_##class; \
10 OBJECT_ENTRY_PRAGMA(class)
You use the OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO
macro primarily for
noncreateable classes that need class-level initialization and
uninitialization. Occasionally, you might want to have a
noncreateable class maintain Registry entries, possibly persistent
class configuration information and component categories.
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 |
---|---|
|
Registers and unregisters the class. The |
|
Initializes and uninitializes the class. |
|
Returns a pointer to a component category map. The
|
|
The |
|
The |
|
Returns the object description text string. |
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:
1class CMyClass : public CComCoClass< ... >, ... {
2public:
3 DECLARE_OBJECT_DESCRIPTION("MyClass Object Description")
4
5 ...
6};
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
.
1static 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 IComponentRegistrar
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:
1#define DECLARE_NO_REGISTRY() \
2 static HRESULT WINAPI UpdateRegistry(BOOL /*bRegister*/) \
3 {return S_OK;}
4
5
6#define DECLARE_REGISTRY(class, pid, vpid, nid, flags)\
7 static HRESULT WINAPI UpdateRegistry(BOOL bRegister) {\
8 return _Module.UpdateRegistryClass(GetObjectCLSID(), \
9 pid, vpid, nid,\
10 flags, bRegister);\
11 }
12
13
14#define DECLARE_REGISTRY_RESOURCE(x)\
15 static HRESULT WINAPI UpdateRegistry(BOOL bRegister) {\
16 ...
17 return ATL::_pAtlModule->UpdateRegistryFromResource(_T(#x), \
18 bRegister); \
19 ...
20 }
21
22
23#define DECLARE_REGISTRY_RESOURCEID(x)\
24 static HRESULT WINAPI UpdateRegistry(BOOL bRegister) {\
25 ...
26 return ATL::_pAtlModule->UpdateRegistryFromResource(x, \
27 bRegister); \
28 ...
29 }
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:
1[NoRemove | ForceRemove | val] Name [ = s | d | m | b 'Value']
2{
3 ... optional script entries for subkeys
4}
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 keys – for example, HKEY_CLASSES_ROOT
– as well as
their four character abbreviations (HKCR
).
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.
1REGEDIT4
2[HKEY_CLASSES_ROOT\ATLInternals.Demagogue.1]
3@="Demagogue Class"
4[HKEY_CLASSES_ROOT\ATLInternals.Demagogue.1\CLSID]
5@="{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}"
6[HKEY_CLASSES_ROOT\ATLInternals.Demagogue]
7@="Demagogue Class"
8[HKEY_CLASSES_ROOT\ATLInternals.Demagogue\CLSID]
9@="{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}"
10[HKEY_CLASSES_ROOT\ATLInternals.Demagogue\CurVer]
11@="ATLInternals.Demagogue.1"
12[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}]
13@="Demagogue Class"
14[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\ProgID]
15@="ATLInternals.Demagogue.1"
16[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\VersionIndependentProgID]
17@="ATLInternals.Demagogue"
18[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\Programmable]
19[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\InprocServer32]
20@="C:\\ATLINT~1\\Debug\\ATLINT~1.DLL"
21"ThreadingModel"="Apartment"
22[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\TypeLib]
23@="{95CD3721-FC5C-11D1-8CC3-00A0C9C8E50D}"
24[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\Implemented
25Categories]
26[HKEY_CLASSES_ROOT\CLSID\{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}\Implemented
27Categories\{0D22FF22-28CC-11D2-ABDD-00A0C9C8E50D}]
The corresponding Registry script looks like this:
1HKCR
2{
3 ATLInternals.Demagogue.1 = s 'Demagogue Class'
4 {
5 CLSID = s '{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}'
6 }
7 ATLInternals.Demagogue = s 'Demagogue Class'
8 {
9 CLSID = s '{95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D}'
10 CurVer = s 'ATLInternals.Demagogue.1'
11 }
12 NoRemove CLSID
13 {
14 ForceRemove {95CD3731-FC5C-11D1-8CC3-00A0C9C8E50D} = s 'Demagogue
15 Class'
16 {
17 ProgID = s 'ATLInternals.Demagogue.1'
18 VersionIndependentProgID = s 'ATLInternals.Demagogue'
19 ForceRemove 'Programmable'
20 InprocServer32 = s '%MODULE%'
21 {
22 val ThreadingModel = s 'Apartment'
23 }
24 'TypeLib' = s '{95CD3721-FC5C-11D1-8CC3-00A0C9C8E50D}'
25 'Implemented Categories'
26 {
27 {0D22FF22-28CC-11D2-ABDD-00A0C9C8E50D}
28 }
29 }
30 }
31}
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.
1// resource.h file
2#define IDR_DEMAGOGUE 102
3
4// Server.rc file
5IDR_DEMAGOGUE REGISTRY DISCARDABLE "Demagogue.rgs"
6EARPOLITIC REGISTRY DISCARDABLE "EarPolitic.rgs"
7
8// Demagogue.h file
9class ATL_NO_VTABLE CDemagogue :
10 public CComObjectRootEx<CComSingleThreadModel>,
11 public CComCoClass<CDemagogue, &CLSID_Demagogue>,
12
13...
14public:
15DECLARE_REGISTRY_RESOURCEID(IDR_DEMAGOGUE)
16
17...
18};
19
20// EarPolitic.h file
21class ATL_NO_VTABLE CEarPolitic :
22 public CComObjectRootEx<CComSingleThreadModel>,
23 public CComCoClass<CEarPolitic, &CLSID_EarPolitic>,
24
25...
26{
27public:
28DECLARE_REGISTRY_RESOURCE(EARPOLITIC)
29...
30};
Registry Script Variables
Note that in the Registry script, one of the
lines references a symbol called %MODULE%
.
1InprocServer32 = 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:
1InprocServer32 = 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:
1DateInstalled = 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:
1HRESULT AddCommonRGSReplacements(IRegistrarBase *pRegistrar) {
2 BaseModule::AddCommonRGSReplacements( pRegistrar );
3 OLECHAR wszDate [16]; SYSTEMTIME st;
4 GetLocalTime(&st);
5 wsprintfW(wszDate, L"%.4d/%.2d/%.2d", st.wYear,
6 st.wMonth, st.wDay);
7 pRegistrar->AddReplacement( OLESTR("INSTALLDATE"), wszDate );
8}
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:
1static HRESULT WINAPI UpdateRegistry(BOOL b) {
2 OLECHAR wszDate [16]; SYSTEMTIME st;
3 GetLocalTime(&st);
4 wsprintfW(wszDate, L"%.4d/%.2d/%.2d", st.wYear,
5 st.wMonth, st.wDay);
6 _ATL_REGMAP_ENTRY rm[] = {
7 { OLESTR("INSTALLDATE"), wszDate},
8 { 0, 0 } };
9 return _pAtlModule->UpdateRegistryFromResource(
10 IDR_DEMAGOGUE, b, rm);
11}
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:
1static 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:
1// {0D22FF22-28CC-11d2-ABDD-00A0C9C8E50D}
2static const GUID CATID_ATLINTERNALS_SAMPLES =
3{0xd22ff22, 0x28cc, 0x11d2, {0xab, 0xdd, 0x0, 0xa0, 0xc9, 0xc8,
4 0xe5, 0xd}};
5
6BEGIN_CATEGORY_MAP(CDemagogue)
7IMPLEMENTED_CATEGORY(CATID_ATLINTERNALS_SAMPLES)
8END_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.
1#define BEGIN_CATEGORY_MAP(x)\
2 static const struct ATL::_ATL_CATMAP_ENTRY* GetCategoryMap() {\
3 static const struct ATL::_ATL_CATMAP_ENTRY pMap[] = {
The IMPLEMENTED_CATEGORY
and REQUIRED_CATEGORY
macros populate the table with the
appropriate entries:
1#define IMPLEMENTED_CATEGORY(catid) { \
2 _ATL_CATMAP_ENTRY_IMPLEMENTED, &catid },
3#define REQUIRED_CATEGORY(catid) { \
4 _ATL_CATMAP_ENTRY_REQUIRED, &catid },
The END_CATEGORY_MAP
adds a delimiting
entry to the table and completes the GetCategoryMap
function:
1#define END_CATEGORY_MAP()\
2 { _ATL_CATMAP_ENTRY_END, NULL } };\
3 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.
1struct _ATL_CATMAP_ENTRY {
2 int iType;
3 const CATID* pcatid;
4};
5
6
7#define _ATL_CATMAP_ENTRY_END 0
8#define _ATL_CATMAP_ENTRY_IMPLEMENTED 1
9#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:
1HKCR
2{
3 NoRemove AppID
4 {
5 {A11552A2-28DF-11d2-ABDD-00A0C9C8E50D} = s 'ATLInternals'
6 'ATLInternals.DLL'
7 {
8 val AppID = s {A11552A2-28DF-11d2-ABDD-00A0C9C8E50D}
9
10 }
11 }
12 NoRemove 'Component Categories'
13 {
14 {0D22FF22-28CC-11d2-ABDD-00A0C9C8E50D}
15 {
16 val 409 = s 'ATL Internals Example Components'
17 }
18 }
19}
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 inthe 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
:
1// In the resource.h file
2#define IDR_MATHSERVER 100
3
4// In the MathServer.rc file
5IDR_MATHSERVER REGISTRY "MathServer.rgs"
6
7// MathServer.rgs file
8HKCR
9{
10 NoRemove AppID
11{
12 '%APPID%' = s 'MathServer'
13 'MathServer.DLL'
14 {
15 val AppID = s '%APPID%'
16 }
17 }
18}
The RGS files are similar for all three server types – inproc, 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
.
1template <class T>
2class ATL_NO_VTABLE CAtlDllModuleT : public CAtlModuleT<T> {
3public:
4 ...
5 HRESULT DllRegisterServer(BOOL bRegTypeLib = TRUE) {
6 ...
7 T* pT = static_cast<T*>(this);
8 HRESULT hr = pT->RegisterAppId(); // execute server script
9 ...
10 return hr;
11 }
12 ...
13};
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.
1template <class T>
2class CAtlExeModuleT : public CAtlModuleT<T> {
3public :
4
5 int WinMain(int nShowCmd) throw() {
6 ...
7 HRESULT hr = S_OK;
8
9 LPTSTR lpCmdLine = GetCommandLine();
10 if (pT->ParseCommandLine(lpCmdLine, &hr) == true)
11 hr = pT->Run(nShowCmd);
12
13 ...
14 }
15
16 bool ParseCommandLine(LPCTSTR lpCmdLine, HRESULT* pnRetCode) {
17 *pnRetCode = S_OK;
18
19 TCHAR szTokens[] = _T("-/");
20
21 T* pT = static_cast<T*>(this);
22 LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);
23 while (lpszToken != NULL) {
24 if (WordCmpI(lpszToken, _T("UnregServer"))==0) {
25 *pnRetCode = pT->UnregisterServer(TRUE); // all class script
26 if (SUCCEEDED(*pnRetCode))
27 *pnRetCode = pT->UnregisterAppId(); // server scripts
28 return false;
29 }
30
31 // Register as Local Server
32 if (WordCmpI(lpszToken, _T("RegServer"))==0) {
33 *pnRetCode = pT->RegisterAppId(); // server script
34 if (SUCCEEDED(*pnRetCode))
35 *pnRetCode = pT->RegisterServer(TRUE); // all class scripts
36 return false;
37 }
38
39 lpszToken = FindOneOf(lpszToken, szTokens);
40 }
41
42 return true;
43 }
44};
Class Initialization and Uninitialization
The ObjectMain Method
In C++, constructors and
destructors are used to provide instance-level initialization and
cleanup – that 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. Youalways have a default implementation, provided by CComObjectRootBase
, that does nothing and looks like
this:
1static 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]
Instance initialization and termination logic should go in the FinalConstruct
and FinalRelease
methods, as Chapter 4, “Objects in
ATL,” describes.)
1class ATL_NO_VTABLE CSoapBox :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 ...
4{
5public:
6 DECLARE_NO_REGISTRY()
7 ...
8 static void WINAPI ObjectMain(bool bStarting) ;
9};
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.
1ATLINLINE ATLAPI AtlComModuleGetClassObject(
2 _ATL_COM_MODULE* pComModule, REFCLSID rclsid, REFIID riid,
3 LPVOID* ppv) {
4 ...
5 for (_ATL_OBJMAP_ENTRY** ppEntry =
6 pComModule->m_ppAutoObjMapFirst;
7 ppEntry < pComModule->m_ppAutoObjMapLast; ppEntry++) {
8 if (*ppEntry != NULL) {
9 _ATL_OBJMAP_ENTRY* pEntry = *ppEntry;
10 if ((pEntry->pfnGetClassObject != NULL) &&
11 InlineIsEqualGUID(rclsid, *pEntry->pclsid)) {
12 if (pEntry->pCF == NULL) {
13 CComCritSecLock<CComCriticalSection>
14 lock(pComModule->m_csObjMap, false);
15 hr = lock.Lock();
16 ...
17 if (pEntry->pCF == NULL)
18 hr = pEntry->pfnGetClassObject(
19 pEntry->pfnCreateInstance,
20 __uuidof(IUnknown),
21 (LPVOID*)&pEntry->pCF);
22 }
23 if (pEntry->pCF != NULL)
24 hr = pEntry->pCF->QueryInterface(riid, ppv);
25 break;
26 }
27 }
28 }
29 if (*ppv == NULL && hr == S_OK)
30 hr = CLASS_E_CLASSNOTAVAILABLE;
31 return hr;
32}
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.
1// Obtain a Class Factory (DLL only)
2HRESULT GetClassObject(REFCLSID rclsid, REFIID riid,
3 LPVOID* ppv);
You use it like this:
1IClassFactory* pFactory;
2HRESULT hr = _pAtlModule->GetClassObject (
3 CLSID_Demagogue, IID_IClassFactory,
4 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.
1ATLINLINE ATLAPI AtlComModuleRegisterClassObjects(
2 _ATL_COM_MODULE* pComModule,
3 DWORD dwClsContext, DWORD dwFlags) {
4 ...
5 HRESULT hr = S_FALSE;
6 for (_ATL_OBJMAP_ENTRY** ppEntry = pComModule->m_ppAutoObjMapFirst;
7 ppEntry < pComModule->m_ppAutoObjMapLast && SUCCEEDED(hr);
8 ppEntry++) {
9 if (*ppEntry != NULL)
10 hr = (*ppEntry)->RegisterClassObject(dwClsContext,
11 dwFlags);
12 }
13 return hr;
14}
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:
1struct _ATL_OBJMAP_ENTRY {
2 HRESULT WINAPI RegisterClassObject(DWORD dwClsContext,
3 DWORD dwFlags) {
4 IUnknown* p = NULL;
5 if (pfnGetClassObject == NULL) return S_OK;
6 HRESULT hRes = pfnGetClassObject(pfnCreateInstance,
7 __uuidof(IUnknown), (LPVOID*) &p);
8 if (SUCCEEDED(hRes))
9 hRes = CoRegisterClassObject(*pclsid, p,
10 dwClsContext, dwFlags, &dwRegister);
11 if (p != NULL) p->Release();
12 return hRes;
13 }
14};
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.
1struct _ATL_OBJMAP_ENTRY30 {
2 ...
3 _ATL_CREATORFUNC* pfnGetClassObject; // Creates a class object
4 _ATL_CREATORFUNC* pfnCreateInstance; // Create a class instance
5 ...
6};
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.
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.
1typedef 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.
1#define OBJECT_ENTRY_AUTO(clsid, class) \
2 __declspec(selectany) ATL::_ATL_OBJMAP_ENTRY \
3 __objMap_##class = \
4 {&clsid, class::UpdateRegistry, \
5 class::_ClassFactoryCreatorClass::CreateInstance, \
6 class::_CreatorClass::CreateInstance, NULL, 0, \
7 class::GetObjectDescription, class::GetCategoryMap, \
8 class::ObjectMain }; \
9 extern "C" __declspec(allocate("ATL$__m"))\
10 __declspec(selectany) \
11 ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = \
12 &__objMap_##class; \
13 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.
1template <class T, const CLSID* pclsid = &CLSID_NULL>
2class CComCoClass {
3public:
4 DECLARE_CLASSFACTORY()
5 DECLARE_AGGREGATABLE(T)
6 ...
7};
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.
1#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)
2
3#if defined(_WINDLL) | defined(_USRDLL)
4#define DECLARE_CLASSFACTORY_EX(cf) \
5 typedef ATL::CComCreator< \
6 ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;
7#else
8// don't let class factory refcount influence lock count
9#define DECLARE_CLASSFACTORY_EX(cf) \
10 typedef ATL::CComCreator< \
11 ATL::CComObjectNoLock< cf > > _ClassFactoryCreatorClass;
12#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:
1// Inproc server class object instantiation
2ATLINLINE ATLAPI AtlComModuleGetClassObject(_ATL_COM_MODULE* pM,
3 REFCLSID rclsid,
4 REFIID riid, LPVOID* ppv) {
5 ...
6 hr = pEntry->pfnGetClassObject(pEntry->pfnCreateInstance,
7 __uuidof(IUnknown),
8 (LPVOID*)&pEntry->pCF);
9 ...
10} ;
11
12// Out of process server class object instantiation
13struct _ATL_OBJMAP_ENTRY {
14 HRESULT WINAPI RegisterClassObject(DWORD dwClsContext,
15 DWORD dwFlags) {
16 ...
17 HRESULT hRes = pfnGetClassObject(pfnCreateInstance,
18 _uuidof(IUnknown), (LPVOID*) &p);
19 ...
20 }
21};
Recall that the pfnGetClassObject
member variable is set by the OBJECT_ENTRY_AUTO
macro and
contains a pointer to the CreateInstance
method of _ClassFactoryCreatorClass
:
1class::_ClassFactoryCreatorClass::CreateInstance
For an inproc server, this evaluates to the
following (assuming that you’re using the default class factory
class, which is CComClassFactory
):
1class::ATL::CComCreator<
2 ATL::CComObjectCached< ATL::CComClassFactory >
3 >::CreateInstance
For an out-of-process server, this evaluates to:
1class::ATL::CComCreator<
2 ATL::CComObjectNoLock< ATL::CComClassFactory >
3 >::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:
1template <class T1>
2class CComCreator {
3public:
4 static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
5 LPVOID* ppv) {
6 ...
7 ATLTRY(p = new T1(pv))
8 if (p != NULL) {
9 p->SetVoid(pv);
10 ...
11 }
12 return hRes;
13 }
14};
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:
1class CComObjectRootBase {
2 ...
3 void SetVoid(void*) {}
4};
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:
1class CComClassFactory :
2 public IClassFactory,
3 public CComObjectRootEx<CComGlobalsThreadModel> {
4public:
5 ...
6 void SetVoid(void* pv) { m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;}
7 _ATL_CREATORFUNC* m_pfnCreateInstance;
8};
Let’s look at the class object creation code in the ATL helper function again:
1HRESULT hRes = pfnGetClassObject( pfnCreateInstance,
2 __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:
1template <class T>
2class CComClassFactory :
3...
4{
5 ...
6 STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,
7 void** ppvObj) {
8 ...
9 ATLTRY(p = new T ());
10 ...
11 }
12};
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.
1class CComClassFactory :
2 public IClassFactory,
3 public CComObjectRootEx<CComGlobalsThreadModel> {
4public:
5 BEGIN_COM_MAP(CComClassFactory)
6 COM_INTERFACE_ENTRY(IClassFactory)
7 END_COM_MAP()
8
9 // IClassFactory
10 STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,
11 void** ppvObj);
12 STDMETHOD(LockServer)(BOOL fLock);
13
14 // helper
15 void SetVoid(void* pv);
16
17 _ATL_CREATORFUNC* m_pfnCreateInstance;
18};
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.
1STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,
2 void** ppvObj) {
3 ...
4 else hRes = m_pfnCreateInstance(pUnkOuter, riid, ppvObj);
5 }
6 return hRes;
7}
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 Chapter4, “Objects in ATL,” and is set by the following part of the OBJECT_ENTRY_AUTO
macro:
1class::_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.
1template <class T, const CLSID* pclsid = &CLSID_NULL>
2class CComCoClass {
3public:
4 DECLARE_AGGREGATABLE(T)
5 ...
6};
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.
1#define DECLARE_AGGREGATABLE(x) public:\
2 typedef ATL::CComCreator2< \
3 ATL::CComCreator< \
4 ATL::CComObject< x > >, \
5 ATL::CComCreator< \
6 ATL::CComAggObject< x > > \
7 > _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
1class CMyClass : ..., public CComCoClass< ... > {
2public:
3 DECLARE_CLASSFACTORY_EX(CMyClassFactory)
4 ...
5};
6
7class CMyClassFactory : public CComClassFactory {
8 ...
9 STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,
10 void** ppvObj);
11};
ATL also provides three other macros that declare a class factory:
DECLARE_CLASSFACTORY2
. UsesCComClassFactory2
to control creation through a license.1#define DECLARE_CLASSFACTORY2(lic) \ 2 DECLARE_CLASSFACTORY_EX(CComClassFactory2<lic>)
DECLARE_CLASSFACTORY_SINGLETON
. UsesCComClassFactorySingleton
to construct a singleCComObjectCached
object and return the object in response to all instantiation requests.1#define DECLARE_CLASSFACTORY_SINGLETON(obj) \ 2 DECLARE_CLASSFACTORY_EX(CComClassFactorySingleton<obj>)
DECLARE_CLASSFACTORY_AUTO_THREAD
. UsesCComClassFactoryAutoThread
to create new instances in a round-robin manner in multiple apartments.1#define DECLARE_CLASSFACTORY_AUTO_THREAD() \ 2 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.
1template <class license>
2class CComClassFactory2 :
3 public IClassFactory2,
4 public CComObjectRootEx<CComGlobalsThreadModel>,
5 public license {
6public:
7 ...
8
9 STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,
10 void** ppvObj) {
11 ...
12 if (!IsLicenseValid()) return CLASS_E_NOTLICENSED;
13 ...
14 return m_pfnCreateInstance(pUnkOuter, riid, ppvObj);
15 }
16
17 STDMETHOD(CreateInstanceLic)(IUnknown* pUnkOuter,
18 IUnknown* pUnkReserved, REFIID riid, BSTR bstrKey,
19 void** ppvObject);
20 STDMETHOD(RequestLicKey)(DWORD dwReserved, BSTR* pbstrKey);
21 STDMETHOD(GetLicInfo)(LICINFO* pLicInfo);
22 ...
23};
Note that the main
difference between CComClassFactory
and CComClassFactory2
is that the latter class’s CreateInstance
method creates the instance only on a
licensed system – that 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:
|
Returns |
|
Returns a license key as a |
|
Returns |
The following is an example of a simple license class:
1const OLECHAR rlk[] = OLESTR("Some run-time license key") ;
2
3class CMyLicense
4{
5
6protected:
7
8 static BOOL VerifyLicenseKey(BSTR bstr) {
9 return wcscmp (bstr, rlk) == 0;
10 }
11
12 static BOOL GetLicenseKey(DWORD dwReserved, BSTR* pBstr) {
13 *pBstr = SysAllocString(rlk);
14 return TRUE;
15 }
16
17 static BOOL IsLicenseValid() {
18 // Validate that the current system is licensed...
19 // May check for the presence of a specific license file, or
20 // may check for a particular hardware device
21 if (...) return TRUE;
22 }
23};
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
.
1class ATL_NO_VTABLE CEarPolitic :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public CComCoClass<CEarPolitic, &CLSID_EarPolitic>,
4 ...
5{
6public:
7 DECLARE_CLASSFACTORY2 (CMyLicense)
8 ...
9};
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:
1// Obtain pointer to IClassFactory2 interface
2CComPtr<IClassFactory2> pcf;
3::CoGetClassObject(__uuidof(CEarPolitic), CLSCTX_ALL, NULL,
4 __uuidof(pcf), (void**)&pcf);
5
6// Request a run-time license key
7// only succeeds on licensed machine
8CComBSTR bstrKey;
9pcf->RequestLicKey(NULL, &bstrKey);
10
11// Save license key to a file for distribution
12// to run-time clients
13FILE* f = fopen("license.txt", "w+");
14fwrite(bstrKey, sizeof(wchar_t), bstrKey.Length(), f);
15fclose(f);
A user operating on a nonlicensed machine with a runtime license uses that license to create instances, like this:
1// Obtain a pointer to the IClassFactory2 interface
2CComPtr<IClassFactory2> pcf;
3::CoGetClassObject(__uuidof(CEarPolitic), CLSCTX_ALL, NULL,
4 __uuidof(pcf), (void**)&pcf);
5
6// Read in the run-time license key from disk
7WCHAR szKey[1025];
8FILE* f = fopen("license.txt", "r");
9int n = fread((void**)&szKey, sizeof(wchar_t), 1024, f);
10szKey[n] = '\0';
11fclose(f);
12
13// Create an instance of the licensed object w/ the license key
14CComBSTR bstrKey(szKey);
15CComPtr<IEarPolitic> p;
16hr = pcf->CreateInstanceLic(NULL, NULL, __uuidof(p), bstrKey,
17 (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.
1template <class T>
2class CComClassFactorySingleton : public CComClassFactory {
3public:
4 // IClassFactory
5 STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,
6 void** ppvObj) {
7 ...
8 hRes = m_spObj->QueryInterface(riid, ppvObj);
9 ...
10 return hRes;
11 }
12 CComPtr<IUnknown> m_spObj;
13};
The following is an example of a simple singleton class:
1class CMyClass : ..., public CComCoClass< ... > {
2public:
3 DECLARE_CLASSFACTORY_SINGLETON(CMyClass)
4
5 ...
6};
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 best – and 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.
1class CAccount {
2public:
3 void Audit() { ... }
4 void Open() { ... }
5 void Close() { ... }
6 double Deposit(double dAmount) { ... }
7 double Withdraw(double dAmount) { ... }
8};
9
10static CAccount s_account; // shared state
11static CComAutoCriticalSection s_lock; // lock for serializing
12 // account access
13
14class CTeller {
15public:
16 void Deposit(double dAmount) {
17 s_lock.Lock();
18 s_account.Deposit(dAmount);
19 s_lock.Unlock();
20 }
21};
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:
1class CComClassFactoryAutoThread :
2 ...
3 STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid,
4 void** ppvObj) {
5 ...
6 hRes = _pAtlAutoThreadModule.CreateInstance(
7 m_pfnCreateInstance,riid, ppvObj);
8 ...
9 }
10 ...
11 _ATL_CREATORFUNC* m_pfnCreateInstance;
12};
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:
1class CMyClass : ..., public CComCoClass< ... > {
2public:
3 DECLARE_CLASSFACTORY_AUTO_THREAD()
4
5 ...
6};
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.
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.
1// project.cpp
2CAtlAutoThreadModule g_AtlAutoModule; // any name will do
ATL declares a global variable of type IAtlAutoThreadModule
in atlbase.h
.
1__declspec(selectany)
2IAtlAutoThreadModule* _pAtlAutoThreadModule;
This global variable is set in the constructor
of CAtlAutoThreadModuleT
, which is the template base class
for CAtlAutoThreadModule
:
1template <class T,
2 class ThreadAllocator = CComSimpleThreadAllocator,
3 DWORD dwWait = INFINITE>
4class ATL_NO_VTABLE CAtlAutoThreadModuleT
5 : public IAtlAutoThreadModule {
6public:
7 CAtlAutoThreadModuleT(
8 int nThreads = T::GetDefaultThreads()) {
9 // only one per server
10 ATLASSERT(_pAtlAutoThreadModule == NULL);
11 _pAtlAutoThreadModule = this;
12 m_pApartments = NULL;
13 m_nThreads= 0;
14 }
15 ...
16};
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
.
1class CComSimpleThreadAllocator {
2public:
3 CComSimpleThreadAllocator() { m_nThread = 0; }
4 int GetThread(CComApartment* /*pApt*/, int nThreads) {
5 if (++m_nThread == nThreads) m_nThread = 0;
6 return m_nThread;
7 }
8 int m_nThread;
9};
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).
1class CRandomThreadAllocator {
2public:
3
4 int GetThread(CComApartment* /*pApt*/, int nThreads) {
5 return rand () % nThreads;
6 }
7};
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.
1// project.cpp
2class CRandomThreadModule :
3public CAtlAutoThreadModuleT< CRandomThreadModule,
4 CRandomThreadAllocator>
5{
6...
7};
8
9CRandomThreadModule 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
.
The CAtlModule Class
All COM servers need to support registration, class objects, and lifetime management. Each type of server provides its registration, class objects, and lifetime management slightly differently, but the basic required functionality is the same for all types of servers.
ATL defines the CAtlModule
base class
and three derived classes, along with numerous helper methods that
support server registration, class objects, and lifetime
management. Each of the three derived classes provides support for
a specific COM server type. CAtlDllModuleT
provides inproc
server functionality; CAtlExeModuleT
and CAtlServiceModuleT
support local servers and Windows
service applications, respectively. Many of the methods in these
classes simply iterate over the entries in the object map and ask
each class in the map to perform the real work.
When you create an ATL COM server project using
the Visual C++ ATL project wizard, it generates relatively
boilerplate code that uses functionality in one of the CAtlModule
-derived classes to implement the server.
An ATL server contains one global instance of
the CAtlModule
class, which the ATL project wizard names _AtlModule
. The _AtlModule
instance also holds
state global to the entire server. You can access this via the
global pointer _pAtlModule
.
The _AtlModule Global Variable
An ATL inproc server uses the CAtlDllModuleT
class directly by declaring a global
instance of a project-specific module class derived from the CAtlDllModuleT
server’s project.cpp
file. Here’s
what the module class definition and global variable declaration
look like in the wizard-generated code for an inproc server called
Math:
1class CMathModule : public CAtlDllModuleT< CMathModule > {
2public:
3 DECLARE_LIBID(LIBID_Math)
4 DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MATH,
5 "{9CB95B71-536A-476a-9244-61363F5C60CA}")
6};
7
8CMathModule _AtlModule;
The DECLARE_LIBID
macro simply sets the m_libid
member variable of the CAtlModule
base class to the GUID value supplied as the
macro parameter. The DECLARE_REGISTRY_APPID_RESOURCEID
macro declares an UpdateRegistryAppId
method and helper
functions for registering the AppId for the server.
The custom module class that the wizard
generates provides a place for you to customize the functionality
of ATL’s module classes. In many places, CAtlDllModuleT
(as well as CExeModuleT
and CServiceModuleT
)
downcasts to your custom module class so that a custom method is
invoked if one is supplied. For instance, look at the
implementation of DllRegisterServer
that CAtlDllModuleT
provides:
1HRESULT DllRegisterServer(BOOL bRegTypeLib = TRUE) {
2 T* pT = static_cast<T*>(this);
3 HRESULT hr = pT->RegisterAppId();
4 if (SUCCEEDED(hr))
5 hr = pT->RegisterServer(bRegTypeLib);
6 return hr;
7}
Here, if your CMathModule
had defined a
custom version of RegisterAppId
or RegisterServer
, those custom versions would be invoked in
lieu of the default implementation provided in CAtlDllModuleT
.
A local server defines a specialized version of CAtlModule
, called CAtlExeModuleT
, in the
server’s project.cpp
file:
1class CMathModule : public CAtlExeModuleT< CMathModule > {
2public:
3 DECLARE_LIBID(LIBID_Math)
4 DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MATH,
5 "{9CB95B71-536A-476a-9244-61363F5C60CA}")
6};
7
8CMathModule _AtlModule;
A service-based server also defines a
specialized version of CAtlModule
, called CAtlServiceModuleT
, in the server’s project.cpp
file:
1class CMathModule :
2 public CAtlServiceModuleT< CMathModule, IDS_SERVICENAME >
3{
4public:
5 DECLARE_LIBID(LIBID_Math)
6 DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MATH,
7 "{9CB95B71-536A-476a-9244-61363F5C60CA}")
8};
9
10CMathModule _AtlModule;
The CAtlModule Registration Support
The CAtlModuleT
class has extensive support for registration and unregistration of
COM objects and servers.
The RegisterServer and UnregisterServer Methods
The RegisterServer
and UnregisterServer
methods add or remove standard class
registration information (ProgID
, version-independent ProgID
, class description, and threading model) into or
from the system Registry. These methods call upon a global instance
of CAtlComModule
, which, in turn, uses the AtlComModuleRegisterServer
and AtlComModuleUnregisterServer
helper functions to perform
the actual task.
1HRESULT RegisterServer(BOOL bRegTypeLib = FALSE,
2 const CLSID* pCLSID = NULL);
3HRESULT UnregisterServer(BOOL bUnRegTypeLib = FALSE,
4 const CLSID* pCLSID= NULL);
The RegisterServer
method updates the
system Registry for a single class object when the pCLSID
parameter is non-NULL
. When the parameter is NULL
, the method calls the UpdateRegistry
method
for all classes listed in the object map. When the bRegTypeLib
parameter is TRUE
, the method also
registers the type library.
The UnregisterServer
method removes the
Registry entries for a single class when the pCLSID
parameter is non-NULL
. When the parameter is NULL
, the method calls the UpdateRegistry
method
for all classes listed in the object map. The overloaded method
that accepts a bUnRegTypeLib
parameter also unregisters
the type library when the parameter is TRUE
.
Inproc servers call upon this support from their DllRegisterServer
and DllUnregisterServer
functions:
1STDAPI DllRegisterServer(void) {
2 return _AtlModule.DllRegisterServer();
3}
4
5STDAPI DllUnregisterServer(void) {
6 return _AtlModule.DllUnregisterServer();
7}
The calls to CAtlDllModuleT
’s DllRegisterServer
and DllUnregisterServer
functions invoke RegisterServer
and UnregisterServer
on the CAtlModule
base class. The calls to RegisterAppId
and UnRegisterAppId
execute the server’s registration
script. The DECLARE_REGISTRY_APPID_AND_RESOURCEID
macro we
supplied in our CAtlModule
-derived class defined an UpdateRegistryAppId
function that runs the server’s
registration script.
1template <class T>
2class ATL_NO_VTABLE CAtlDllModuleT : public CAtlModuleT<T> {
3public :
4 ...
5 HRESULT DllRegisterServer(BOOL bRegTypeLib = TRUE) {
6 T* pT = static_cast<T*>(this);
7 // server script
8 HRESULT hr = pT->RegisterAppId();
9 if (SUCCEEDED(hr))
10 // all class scripts
11 hr = pT->RegisterServer(bRegTypeLib);
12 return hr;
13 }
14 HRESULT DllUnregisterServer(BOOL bUnRegTypeLib = TRUE) {
15 T* pT = static_cast<T*>(this);
16 // all class scripts
17 HRESULT hr = pT->UnregisterServer(bUnRegTypeLib);
18 if (SUCCEEDED(hr))
19 // server script
20 hr = pT->UnregisterAppId();
21 return hr;
22 }
23 ...
24};
With a local server, the ATL wizard generates a WinMain
that simply delegates to CAtlExeModuleT::WinMain
. This code parses the command line
to determine which action (register or unregister) to take and then
delegates the work to CAtlModule
.
1bool CAtlExeModuleT< T >::ParseCommandLine(LPCTSTR lpCmdLine,
2 HRESULT* pnRetCode) {
3 ...
4
5 if (WordCmpI(lpszToken, _T("UnregServer"))==0) {
6 *pnRetCode = pT->UnregisterServer(TRUE);
7 if (SUCCEEDED(*pnRetCode))
8 *pnRetCode = pT->UnregisterAppId();
9 return false;
10 }
11
12 // Register as Local Server
13 if (WordCmpI(lpszToken, _T("RegServer"))==0) {
14 *pnRetCode = pT->RegisterAppId();
15 if (SUCCEEDED(*pnRetCode))
16 *pnRetCode = pT->RegisterServer(TRUE);
17 return false;
18 }
19
20 ...
21
22 return true;
23}
The UpdateRegistryFromResource Methods
Notice that a COM server
first calls the RegisterAppId
to register the
server-specific entries before registering the class-specific
entries. This method calls the UpdateRegistryAppId
method
that our DECLARE_REGISTRY_APPID_RESOURCEID
macro
generates. Ultimately, the real work is delegated to the UpdateRegistryFromResource
member function.
When you define the preprocessor symbol _ATL_STATIC_REGISTRY
, the UpdateRegistryFromResource
method maps to UpdateRegistryFromResourceS
. When you do not define this
preprocessor symbol, the method maps to UpdateRegistryFromResourceD
. (The S or D stands for Static
or Dynamic.) The difference between the two functions is simple: UpdateRegistryFromResourceS
eventually calls code in atlbase.h
that updates the Registry. The implementation of UpdateRegistryFromResourceD
, on the other hand, calls out
to the ATL Registrar component in atl80.dll
[8] to
do the registration. As a result, if you don’t define _ATL_STATIC_REGISTRY
, you need to distribute atl80.dll
along with your COM server. [9]
Actually, this can be atl7.dll
, atl71.dll
, or atl80.dll
, depending on the version of ATL you’re
compiling against.
atl80.dll
is about 93KB in size. Separate versions of the
registrar DLL used to exist for Win9x and WinNT, but they appear to
have merged in this version of the library. Personally, I recommend
using static linking just to avoid the need to distribute another
DLL because the size cost is fairly minor.
1#ifdef _ATL_STATIC_REGISTRY
2#define UpdateRegistryFromResource UpdateRegistryFromResourceS
3#else
4#define UpdateRegistryFromResource UpdateRegistryFromResourceD
5#endif
6
7 // Resource-based Registration
8#ifdef _ATL_STATIC_REGISTRY
9 // Statically linking to Registry Component
10 HRESULT WINAPIUpdateRegistryFromResourceS(LPCTSTR lpszRes,
11 BOOL bRegister,
12 struct _ATL_REGMAP_ENTRY* pMapEntries = NULL);
13 HRESULT WINAPIUpdateRegistryFromResourceS(UINT nResID,
14 BOOL bRegister,
15 struct _ATL_REGMAP_ENTRY* pMapEntries = NULL);
16#else
17 HRESULT WINAPIUpdateRegistryFromResourceD(LPCTSTR lpszRes,
18 BOOL bRegister,
19 struct _ATL_REGMAP_ENTRY* pMapEntries = NULL) {
20 USES_CONVERSION;
21 return UpdateRegistryFromResourceDHelper(T2COLE(lpszRes),
22 bRegister, pMapEntries);
23 }
24 HRESULT WINAPIUpdateRegistryFromResourceD(UINT nResID,
25 BOOL bRegister,
26 struct _ATL_REGMAP_ENTRY* pMapEntries = NULL) {
27 return UpdateRegistryFromResourceDHelper(
28 (LPCOLESTR)MAKEINTRESOURCE(nResID),
29 bRegister, pMapEntries);
30 }
31#endif
The Type Library Registration Methods
ATL provides a few other registration helper methods:
1// Registry support (helpers)
2HRESULT RegisterTypeLib();
3HRESULT RegisterTypeLib(LPCTSTR lpszIndex);
4HRESULT UnRegisterTypeLib();
5HRESULT UnRegisterTypeLib(LPCTSTR lpszIndex);
As you might expect, RegisterTypeLib
registers the type library contained in
your server’s resources. UnRegisterTypeLib
unregisters it,
of course. These two methods expect the type library to be present
in your server’s resources as a custom resource of type TYPELIB
with integer resource identifier 1. You create
such a resource by adding the following line to your server’s .rc
file.
11 TYPELIB "ATLInternals.tlb"
You can embed multiple type libraries in a server, although it’s an unusual thing to do. You simply need to give them unique integer resource identifiers.
11 TYPELIB "ATLInternals.tlb"
22 TYPELIB "ATLInternalsEx.tlb"
To register and unregister the second type
library, you must call the RegisterTypeLib
or UnRegisterTypeLib
methods, respectively, and specify a
string in the form ``”\N”``,
where ``N`` is the integer
index of the TYPELIB
resource. The following lines
register both type libraries from the previous resource script
example:
1_AtlModule.RegisterTypeLib ();
2_AtlModule.RegisterTypeLib (_T ("\\2"));
CComCoClass Revisited
Earlier in this chapter, you saw how deriving
from CComCoClass
provided your class with a default
implementation of a class factory, as well as default support for
aggregation.
CComCoClass
provides aggregation
support via inheritance of the DECLARE_AGGREGATABLE
macro’s typedef of _CreatorClass
. It also provides the
default implementation of a class factory via inheritance of the DECLARE_CLASS_FACTORY
macro’s _ClassFactoryCreatorClass
typedef.
The Error Methods
CComCoClass
also provides a number of
other useful static methods. Six of the methods are overloaded and
call Error
. They set up the COM Error
object
using its IErrorInfo
interface to provide rich error
information to the client. To call Error
, your object must
implement the ISupportErrorInfo
interface.
When the hRes
parameter is nonzero, Error
returns the value of hRes
. When hRes
is zero, the first four versions of Error
return DISP_E_EXCEPTION
. The remaining two functions
return the result of the macro MAKE_HRESULT(3, FACILITY_ITF, nID)
, creating a custom failure HRESULT
.
1static HRESULT WINAPI
2Error(LPCOLESTR lpszDesc, const IID& iid = GUID_NULL,
3 HRESULT hRes = 0);
4
5static HRESULT WINAPI
6Error(LPCOLESTR lpszDesc, DWORD dwHelpID, LPCOLESTR lpszHelpFile,
7 const IID& iid = GUID_NULL, HRESULT hRes = 0);
8
9static HRESULT WINAPI
10Error(LPCSTR lpszDesc, const IID& iid = GUID_NULL,
11 HRESULT hRes = 0);
12
13static HRESULT WINAPI
14Error(LPCSTR lpszDesc, DWORD dwHelpID, LPCSTR lpszHelpFile,
15 const IID& iid = GUID_NULL, HRESULT hRes = 0);
16
17static HRESULT WINAPI
18Error(UINT nID, const IID& iid = GUID_NULL, HRESULT hRes = 0,
19 HINSTANCE hInst = _AtlBaseModule.GetResourceInstance());
20
21static HRESULT WINAPI
22Error(UINT nID, DWORD dwHelpID, LPCOLESTR lpszHelpFile,
23 const IID& iid = GUID_NULL, HRESULT hRes = 0,
24 HINSTANCE hInst = _AtlBaseModule.GetResourceInstance());
It might seem odd that ATL includes the Error
functionality in CComCoClass
instead of in
a more widely applicable place, such as CComObjectRootEx
.
The reason for this placement is that COM error reporting needs a
CLSID, and the CLSID is stored in CComCoClass
. If you need
to generate rich error information when your class isn’t derived
from CComCoClass
, you can always call the ATL helper
function AtlReportError
. The CComCoClass::Error
methods all use this helper function.
1inline HRESULT WINAPI AtlReportError(const CLSID& clsid,
2 UINT nID,
3 const IID& iid = GUID_NULL,
4 HRESULT hRes = 0,
5 HINSTANCE hInst = _AtlBaseModule.GetResourceInstance());
6
7inline HRESULT WINAPI AtlReportError(const CLSID& clsid,
8 UINT nID, DWORD dwHelpID, LPCOLESTR lpszHelpFile,
9 const IID& iid = GUID_NULL,
10 HRESULT hRes = 0,
11 HINSTANCE hInst = _AtlBaseModule.GetResourceInstance());
12
13inline HRESULT WINAPI AtlReportError(const CLSID& clsid,
14 LPCSTR lpszDesc,
15 DWORD dwHelpID, LPCSTR lpszHelpFile,
16 const IID& iid = GUID_NULL,
17 HRESULT hRes = 0);
18
19inline HRESULT WINAPI AtlReportError(const CLSID& clsid,
20 LPCSTR lpszDesc,
21 const IID& iid = GUID_NULL, HRESULT hRes = 0);
22
23inline HRESULT WINAPI AtlReportError(const CLSID& clsid,
24 LPCOLESTR lpszDesc,
25 const IID& iid = GUID_NULL, HRESULT hRes = 0);
26
27inline HRESULT WINAPI AtlReportError(const CLSID& clsid,
28 LPCOLESTR lpszDesc, DWORD dwHelpID, LPCOLESTR lpszHelpFile,
29 const IID& iid = GUID_NULL, HRESULT hRes = 0);
The Instantiation Methods
CComCoClass
also
provides two useful overloaded CreateInstance
methods that
create an instance of your class, one for creating an aggregated
instance of your class and one that creates a nonaggregated
instance:
1template <class Q>
2static HRESULT CreateInstance(IUnknown* punkOuter, Q** pp) {
3 return T::_CreatorClass::CreateInstance(punkOuter, __uuidof(Q),
4 (void**) pp);
5}
6
7template <class Q>
8static HRESULT CreateInstance(Q** pp) {
9 return T::_CreatorClass::CreateInstance(NULL, __uuidof(Q),
10 (void**) pp);
11}
You use these two methods like this.
1ISpeaker* pSpeaker;
2// Creates non-aggregated instance
3HRESULT hr = CDemagogue::CreateInstance (&pSpeaker);
4// Creates aggregated instance (assuming the class supports aggregation
5HRESULT hr = CDemagogue::CreateInstance (punkOuter, &pSpeaker);
Note that the use of the __uuidof
operator in the template functions means that you
do not have to specify the interface ID for the interface that you
want on the newly instantiated object. The compiler gets the ID
from the type of the interface pointer variable you pass as an
argument to the CreateInstance
method.
ATL and the C Runtime Library
By default, ATL projects link with the C runtime library (CRT) in both debug and release configurations. Although linking with the CRT increases the size of each ATL server, the CRT provides useful functionality for memory management, string manipulation, and exception handling. Additionally, the CRT is responsible for calling the constructors of global objects when an executable is loaded. As a result, constructing anything but the most simplistic of projects without such important CRT functionality is impractical.
In the spirit of allowing developers to minimize
the memory footprint of their COM servers, ATL provides a subset of
CRT functionality that can be used in place of the CRT itself. By
defining the preprocessor symbol _ATL_MIN_CRT
, ATL
projects will not link with the CRT. You can define this symbol
from the project properties, as shown in Figure 5.2.
Figure 5.2. Defining _ATL_MIN_CRT
in an ATL server

Setting Minimize CRT Use in ATL causes _ATL_MIN_CRT
to be defined and keeps your server from
linking with the CRT. It’s important to realize that ATL does not
make available all the functions that the CRT provides when this
option is in use. If you happen to use a function that requires the
CRT when _ATL_MIN_CRT
is defined, you’ll get the following
somewhat cryptic linker error:
1LIBCMT.LIB(crt0.obj) : error LNK2001:
2unresolved external symbol _main
In ATL 3 and earlier, using _ATL_MIN_CRT
left
out one very important feature of the CRT: static and global object
initialization. Global and static objects would not have their constructors or destructors
called. Thankfully, ATL 7 and later provide an implementation of
this feature even without CRT support. Inproc ATL servers include
an implementation of _DllMainCRTStartup
that invokes
global constructors and destructors. ATL itself relies upon global
constructors and destructors in many key places throughout the
framework. For instance, in the discussion of ObjectMain
presented earlier in this chapter, I told you that ATL invokes ObjectMain
when a server initializes without explaining
precisely how this was done. With global constructors available
both with and without _ATL_MIN_CRT
defined, ATL is
perfectly safe to invoke the ObjectMain
methods of each
class in the object map by using the constructor of the global CAtlModule
instance:
1template <class T>
2class ATL_NO_VTABLE CAtlDllModuleT : public CAtlModuleT<T> {
3public :
4 CAtlDllModuleT() {
5 _AtlComModule.ExecuteObjectMain(true);
6 }
7
8 ~CAtlDllModuleT() {
9 _AtlComModule.ExecuteObjectMain(false);
10 }
11 ...
12}
Summary
The object map is the primary data table that
the CAtlModule
-derived _AtlModule
object uses to
register and unregister the classes in the server. CAtlModule
provides numerous helper methods you can use to
register and unregister your server, the classes in the server, and
the component categories in the classes.
An ATL server creates a class factory for a
class, as needed, using the class-creator function entry in the
object map. This class factory then creates instances of the class
using the instance-creator function entry in the object map.
Typically, you inherit a default specification for the
implementation of these two creator classes when you derive your
class from CComCoClass
. However, with the appropriate
macro entries in your class, you can override these defaults and
provide a custom class factory and custom instance creator
function.
CComCoClass
has some useful utility
functions for reporting rich errors to a client using the COM ErrorInfo
protocol. The class also has convenient
instantiation methods that create an instance and query for the
proper interface based on the type of the provided interface
pointer variable.