previous page
next page

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.[3]

[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:

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.

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.

#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.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)