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:
class CMyClass : public CComCoClass< ... >, ...
{
public:
...
};
OBJECT_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:
struct _ATL_OBJMAP_ENTRY30 {
const CLSID* pclsid;
HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
_ATL_CREATORFUNC* pfnGetClassObject;
_ATL_CREATORFUNC* pfnCreateInstance;
IUnknown* pCF;
DWORD dwRegister;
_ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
_ATL_CATMAPFUNC* pfnGetCategoryMap;
void (WINAPI *pfnObjectMain)(bool bStarting);
};
typedef _ATL_OBJMAP_ENTRY30 _ATL_OBJMAP_ENTRY;
The
structure contains the following fields, many of which are pointers
to functions:
Field
|
Description
|
pclsid
|
Pointer to CLSID for this class entry
|
pfnUpdateRegistry
|
The function that registers and unregisters the
class
|
pfnGetClassObject
|
The Creator function that creates an instance of
the class object
|
pfnCreateInstance
|
The Creator function that creates an instance of
the class
|
pCF
|
Pointer to the class object
instanceNULL if not yet created
|
dwRegister
|
Registration cookie
CoRegisterClassObject returns
|
pfnGetObjectDescription
|
The function that returns the object description
for the class
|
pfnGetCategoryMap
|
The function that returns the component category
map
|
pfnObjectMain
|
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.
#define OBJECT_ENTRY_AUTO(clsid, class) \
__declspec(selectany) ATL::_ATL_OBJMAP_ENTRY \
__objMap_##class = \
{&clsid, class::UpdateRegistry, \
class::_ClassFactoryCreatorClass::CreateInstance, \
class::_CreatorClass::CreateInstance, NULL, 0, \
class::GetObjectDescription, class::GetCategoryMap, \
class::ObjectMain }; \
extern "C" __declspec(allocate("ATL$__m"))\
__declspec(selectany) \
ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = \
&__objMap_##class; \
OBJECT_ENTRY_PRAGMA(class)
A 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:
#if defined(_M_IX86)
#define OBJECT_ENTRY_PRAGMA(class)
__pragma(comment(linker, "/include:___pobjMap_" #class));
...
#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
NULLs. The code in atlbase.h that does this is
shown here:
#pragma section("ATL$__a", read, shared)
#pragma section("ATL$__z", read, shared)
#pragma section("ATL$__m", read, shared)
extern "C" {
__declspec(selectany) __declspec(allocate("ATL$__a"))
_ATL_OBJMAP_ENTRY* __pobjMapEntryFirst = NULL;
__declspec(selectany) __declspec(allocate("ATL$__z"))
_ATL_OBJMAP_ENTRY* __pobjMapEntryLast = NULL;
}
#if !defined(_M_IA64)
#pragma comment(linker, "/merge:ATL=.rdata")
#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.
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:
class CDog : public CComCoClass< ... >, ...
{ };
OBJECT_ENTRY_AUTO(__uuidof(Dog), CDog)
class CCat: public CComCoClass< ... >, ...
{ };
OBJECT_ENTRY_AUTO(__uuidof(Cat), CCat)
class CMouse: public CComCoClass< ... >, ...
{ };
OBJECT_ENTRY_AUTO(__uuidof(Mouse), CMouse)
The actual layout of the object map in memory
looks something like Figure
5.1.
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.
#define OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO(clsid, class) \
__declspec(selectany) ATL::_ATL_OBJMAP_ENTRY \
__objMap_##class = \
{&clsid, class::UpdateRegistry, NULL, NULL, NULL, 0, NULL, \
class::GetCategoryMap, class::ObjectMain }; \
extern "C" __declspec(allocate("ATL$__m" )) \
__declspec(selectany) \
ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = \
&__objMap_##class; \
OBJECT_ENTRY_PRAGMA(class)
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.
|