| < Free Open Study > |
|
The ATL COM map is a data structure used by the framework to determine if your coclass has a requested interface, and if so, to determine how to return a reference to the client. When a client is attempting to obtain an interface from an ATL coclass, the framework responds with the following sequence of events:
CComObject<>::QueryInterface() is called by the framework.
CComObject<>::QueryInterface() calls _InternalQueryInterface(). As we will see, this helper method is supplied by the BEGIN_COM_MAP macro.
_InternalQueryInterface() then calls CComObjectRootBase::InternalQueryInterface().
Finally, CComObjectRootBase::InternalQueryInterface() calls a helper function named AtlInternalQueryInterface(), which interrogates the COM map to obtain a given interface.
Every ATL coclass maintains a COM map, which is established using the BEGIN_COM_ MAP and END_COM_MAP macros. The ATL Object Wizard always includes support for the initial default interface (as specified by the Names tab) with the COM_INTERFACE_ ENTRY macro. As we specified IDraw as the default interface for CoRectangle, our initial COM map looks like the following:
// The COM map is ATL's way to provide a lookup table for your coclass's // set of supported interfaces. class ATL_NO_VTABLE CCoRectangle : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoRectangle, &CLSID_CoRectangle>, public IDraw { ... BEGIN_COM_MAP(CCoRectangle) COM_INTERFACE_ENTRY(IDraw) END_COM_MAP() ... };
Some aspects of the COM map should be clear by now. First, if you do not have a listing for your custom interfaces in this map (typically using COM_INTERFACE_ENTRY), external clients will not be able to obtain an interface reference. Forgetting to add a listing in your COM map is just as offensive as forgetting to add an if(riid == IID_XXX) provision in a raw C++ QueryInterface() implementation.
However, understanding exactly what these magic macros are doing is not altogether obvious. To begin, let's look at the BEGIN_COM_MAP macro.
This macro is used to begin the definition of your map. Notice that the only parameter is the name of the defining class (CCoRectangle). When the BEGIN_COM_MAP macro expands, a number of new methods and data members are inserted into your class's public sector. Here is a brief description of each item:
Item Inserted by BEGIN_COM_MAP | Meaning in Life |
---|---|
typedef x | Your class (in this case CCoRectangle) is typedef-ed to _ComMapClass. |
_GetRawUnknown() GetUnknown() | These methods are used to fetch the IUnknown interface for a given coclass. |
_InternalQueryInterface() | This is a helper method called by CComObject<>, which in turn calls InternalQueryInterface(). |
_entries[] | An array of _ATL_INTMAP_ENTRY structures. |
_GetEntries() | This helper function returns the array of _ATL_INTMAP_ENTRY structures. |
Armed with this overview, here is the definition of BEGIN_COM_MAP (<atlcom.h>):
// The parameter to this macro is the name of the class defining the map. #define BEGIN_COM_MAP(x) public: \ // Friendly typedef of your class. \ typedef x _ComMapClass; \ // Function to extract raw IUnknown pointer. \ IUnknown* _GetRawUnknown() \ { ATLASSERT(_GetEntries()[0].pFunc == _ATL_SIMPLEMAPENTRY); \ return (IUnknown*)((int)this+_GetEntries()->dw); } \ IUnknown* GetUnknown() {return _GetRawUnknown(); } \ // Tests for a given IID by calling InternalQueryInterface(). \ HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) \ { return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); } \ // Define an array of _ATL_INTMAP_ENTRY structures, \ // and a method to return this array.\ const static _ATL_INTMAP_ENTRY* WINAPI _GetEntries() { \ static const _ATL_INTMAP_ENTRY _entries[] = { DEBUG_QI_ENTRY(x)
A COM map is, in reality, a NULL-terminated array of _ATL_INTMAP_ENTRY structures. Each structure in the array provides detailed information used to determine the vPtr for a given interface. The _ATL_INTMAP_ENTRY array is used by AtlInternal- QueryInterface() to test for a given interface ID and return the associated vPtr to the calling client. The BEGIN_COM_MAP macro names this array of structures _entries[]. Here is the _ATL_INTMAP_ENTRY structure, as defined in <atlbase.h>:
// A COM map is essentially an array of _ATL_INTMAP_ENTRY structures. struct _ATL_INTMAP_ENTRY { const IID* piid; // The GUID of this interface. DWORD dw; // Offset to vPtr. _ATL_CREATORARGFUNC* pFunc; // How do I find the offset? };
The first field of _ATL_INTMAP_ENTRY is the REFIID for the given interface, for example &IID_IDraw. The second DWORD field contains the offset to the vPtr for a given interface. This offset is calculated in various ways based off the final field, pFunc, which will usually be set to the default value of _ATL_SIMPLEMAPENTRY (#defined as 1). If pFunc is equal to _ATL_SIMPLEMAPENTRY, this informs the framework to calculate the offset to the vPtr using the coclass's this pointer.
If pFunc is not set to _ATL_SIMPLEMAPENTRY, this instructs the framework to call the "function pointed to" by pFunc to calculate the offset to the vPtr. These alternative helper functions will be used to find a vPtr if the interface in question is implemented using more exotic techniques such as tear-off interfaces, aggregation, or interface chaining.
We have already seen that CComObjectRootBase does provide a small set of helper functions (_Chain(), _Break(), _NoInterface(), _Cache(), _Delegate(), and _Creator()) for these purposes, but for the time being we will assume that pFunc is set to _ATL_SIMPLEMAPENTRY.
BEGIN_COM_MAP also defines the _GetEntries() method, which returns the array of _ATL_INTMAP_ENTRY structures (a.k.a. _entries[]):
// _GetEntries() returns the _entries[] array. const static _ATL_INTMAP_ENTRY* WINAPI _GetEntries() { \ static const _ATL_INTMAP_ENTRY _entries[] = { DEBUG_QI_ENTRY(x)
The COM map adds a method named _InternalQueryInterface() which delegates to CComObjectRootBase::InternalQueryInterface(). _InternalQueryInterface() passes the _ATL_INTMAP_ENTRY array (via a call to _GetEntries()), the REFIID of the interface, and an [out] parameter to hold the vPtr of the interface in question:
// Your COM map defines _InternalQueryInterface() which passes the _entries array // to InternalQueryInterface() via _GetEntries(). HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) \ { return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); } \
Finally, BEGIN_COM_MAP provides a function called _GetRawUnknown(), which returns the IUnknown pointer for the coclass. _GetRawUnknown() first retrieves the array of _ATL_INTMAP_ENTRY structures with a call to _GetEntries(). A check is made to ensure that the first entry in the COM map is a simple map entry. Using the DWORD field of the first index in the array, _GetRawUnknown() adjusts the pointer offset to return the correct vPtr for IUnknown. If you ever have a need to fetch your own IUnknown pointer, call GetUnknown():
// The very first entry in the COM map is used to determine the offset to your // IUnknown pointer. IUnknown* _GetRawUnknown() \ { ATLASSERT(_GetEntries()[0].pFunc == _ATL_SIMPLEMAPENTRY); \ return (IUnknown*)((int)this+_GetEntries()->dw); } \ IUnknown* GetUnknown() {return _GetRawUnknown(); } \
Because the first entry in the COM map is used to calculate the vPtr for IID_IUnknown, you will not find an explicit COM_INTERFACE_ENTRY listing for IUnknown. AtlInternalQueryInterface() will examine the first entry of your class's COM map to return IUnknown to the outside world. To ensure this will not fail, be sure the very first entry in your COM map is COM_INTERFACE_ENTRY, COM_INTERFACE_ENTRY2, COM_INTERFACE_ENTRY_IID, or COM_INTERFACE_ENTRY2_IID. Collectively, these four COM map macros are called simple entries in that they all specify pFunc as _ATL_SIMPLEMAPENTRY:
// The ATL "simple" COM map macros all specify pFunc as // _ATL_SIMPLEMAPENTRY. Be sure the very first listing in your COM map is one // of the following macros, or else your IUnknown* will not be calculated correctly. #define COM_INTERFACE_ENTRY(x)\ {&_ATL_IIDOF(x), \ offsetofclass(x, _ComMapClass), \ _ATL_SIMPLEMAPENTRY}, #define COM_INTERFACE_ENTRY_IID(iid, x)\ {&iid,\ offsetofclass(x, _ComMapClass),\ _ATL_SIMPLEMAPENTRY}, #define COM_INTERFACE_ENTRY2(x, x2)\ {&_ATL_IIDOF(x),\ (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\ _ATL_SIMPLEMAPENTRY}, #define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\ {&iid,\ (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\ _ATL_SIMPLEMAPENTRY},
Here is a peek into AtlInternalQueryInterface():
// AtlInternalQueryInterface() finds IUnknown based on the first entry in the map. ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) { ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY); ... // Find IUnknown based off of the first entry in the map. if (InlineIsEqualUnknown(iid)) { IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw); pUnk->AddRef(); *ppvObject = pUnk; return S_OK; } ... }
The END_COM_MAP macro adds in a final _ATL_INTMAP_ENTRY structure to the array containing all NULL fields, signaling the end of the map has been reached. As AtlInternalQueryInterface() iterates over your class's COM map to test for and calculate a vPtr for a COM client, it knows it has reached the end when it encounters all NULL fields:
// Your _entries[] array is terminated with a NULL entry provided by // the END_COM_MAP macro. #define END_COM_MAP() {NULL, 0, 0}}; return _entries;}
So, as you can see, the BEGIN_COM_MAP and END_COM_MAP macros dump quite a bit of information into your coclass's header file. The core pieces to keep in mind are the array of _ATL_INTMAP_ENTRY structures, the _GetEntries() helper function, and the _InternalQueryInterface() method. A partial expansion of an empty COM map would look as so:
// An empty COM map for CoRectangle. class ATL_NO_VTABLE CCoRectangle: public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoRectangle, &CLSID_CoRectangle >, public IDraw { public: ... HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) { return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); } const static _ATL_INTMAP_ENTRY* WINAPI _GetEntries() { static const _ATL_INTMAP_ENTRY _entries[] = { {NULL, 0, 0} }; return _entries; } };
An empty COM map is no fun at all for clients, as this signals our COM object has no interfaces to return. Obviously, we need to examine how to populate the array of _ATL_INTMAP_ENTRY structures with the necessary information for our CoRectangle. This work is simplified by a number of additional COM map macros.
Although your COM map may be populated by numerous macros, the good news is that the COM_INTERFACE_ENTRY macro will do just about everything you need to do for a standard ATL coclass. Just to jog your memory, here is the _ATL_INTMAP_ENTRY structure one more time:
// Recall, this structure holds information for a given interface supported by our coclass. // struct _ATL_INTMAP_ENTRY { const IID* piid; // The GUID of this interface. DWORD dw; // Offset to the vPtr. _ATL_CREATORARGFUNC* pFunc; // Where do I find the offset? };
Each of the various COM map macros provided by ATL will fill the fields of the _ATL_ INTMAP_ENTRY structure in its own unique way. In the case of IDraw, the vPtr is calculated using the COM_INTERFACE_ENTRY macro, defined in <atlcom.h> as the following:
// All of the COM map macros are used to fill the fields of an _ATL_INTMAP_ENTRY // structure. #define COM_INTERFACE_ENTRY(x)\ {&_ATL_IIDOF(x), \ // IID of interface. offsetofclass(x, _ComMapClass), \ // Offset to vPtr held in DWORD. _ATL_SIMPLEMAPENTRY}, // Where to find the offset.
Note that the only parameter to this macro is the name of the interface supported by the coclass. The first field of the structure is the GUID of the named interface, which is determined by the _ATL_IIDOF macro. This macro simply evaluates to __uuidof():
// The first field of the _ATL_INTMAP_ENTRY array is the GUID of the supported // interface. #define _ATL_IIDOF(x) __uuidof(x)
The second field of the structure is the offset of the interface's vPtr from the base. In the case of the COM _INTERFACE_ENTRY macro, the base is the supporting class (CoRectangle). The offsetofclass() macro is used to determine this offset, given the interface (IDraw) and supporting class (CoRectangle).
The third and final field of the _ATL_INTMAP_ENTRY structure specifies a function pointer used to calculate the offset of the vPtr. As COM_INTERFACE_ENTRY automatically sets this field to _ATL_SIMPLEMAPENTRY, we use the coclass itself to calculate the offset.
To help pull this new information together, here is what CoRectangle would look like after the populated COM map has expanded:
// The magic COM map macros define an array of _ATL_INTMAP_ENTRY // structures, which specify how to determine the vPtr for a given interface. class ATL_NO_VTABLE CCoRectangle : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoRectangle, &CLSID_CoRectangle>, public IDraw { ... // BEGIN_COM_MAP(CCoRectangle) // COM_INTERFACE_ENTRY(IDraw) // END_COM_MAP() // The essence of the COM map. HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) { return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); } const static _ATL_INTMAP_ENTRY* WINAPI _GetEntries() { static const _ATL_INTMAP_ENTRY _entries[] = { { &IID_IDraw, offsetofclass(IDraw, CCoRectangle), _ATL_SIMPLEMAPENTRY}, {NULL, 0, 0} }; return _entries; }; ... };
Again, when a client is interested in obtaining an interface from your ATL coclass, the framework passes your COM map to the AtlInternalQueryInterface() method to test for a given interface and fill the client-supplied pointer. Here is the definition of AtlInternalQueryInterface(), as seen in <atlbase.h>:
// AtlInternalQueryInterface() finds interface pointers by examining the COM map. ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) { // Remember! The first entry must be a 'simple entry' ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY); ... // Find IUnknown based off of the first entry in the map. if (InlineIsEqualUnknown(iid)) // use first interface { IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw); pUnk->AddRef(); *ppvObject = pUnk; return S_OK; } // Loop over the COM map until we find a NULL entry. while (pEntries->pFunc != NULL) { // Is this entry the requested IID? BOOL bBlind = (pEntries->piid == NULL); if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) { // If we are a simple entry, use 'this' pointer to get vPtr. if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) { ATLASSERT(!bBlind); IUnknown* pUnk = (IUnknown*) ((int)pThis+pEntries->dw); pUnk->AddRef(); *ppvObject = pUnk; return S_OK; } // Not a simple entry, call helper function to get the vPtr. else { HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw); if (hRes == S_OK || (!bBlind && FAILED(hRes))) return hRes; } } pEntries++; // Go to next entry. } return E_NOINTERFACE; // Did not have the requested IID. }
| < Free Open Study > |
|