The ATL COM Map

[Previous] [Next]

When a client calls QueryInterface on an ATL object, the internal call stack goes like this:

  1. CComObject::QueryInterface
  2. YourClass::_InternalQueryInterface
  3. CComObjectRootBase::InternalQueryInterface
  4. AtlInternalQueryInterface

This progression utilizes the COM map in the object, starting with step 2. _InternalQueryInterface is implemented in an object by the BEGIN_COM_MAP macro. This macro is inserted by default in the object header file when the ATL Object Wizard generates the code. _InternalQueryInterface is implemented like this:

 HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) {      return InternalQueryInterface(this, _GetEntries(),         iid, ppvObject);  } 

Notice that the this pointer and the _GetEntries parameter are passed to CComObjectRootBase::InternalQueryInterface. _GetEntries, another static function implemented by BEGIN_COM _MAP, returns a pointer to an array of _ATL_INTMAP_ENTRY structures. The entries that populate the array depend on the interfaces the object supports for QueryInterface. ATL provides a slew of macros that you can use to make these entries; just insert any of the macros after BEGIN_COM_MAP. Here's a simple COM map that declares QueryInterface support for ISimple1 and IDispatch:

 BEGIN_COM_MAP(CSimple1)     COM_INTERFACE_ENTRY(ISimple1)     COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() 

COM_INTERFACE_ENTRY is defined like this:

 #define COM_INTERFACE_ENTRY(x)\     {&_ATL_IIDOF(x), \     offsetofclass(x, _ComMapClass), \     _ATL_SIMPLEMAPENTRY}, 

The _ATL_INTMAP_ENTRY structure that COM_INTERFACE_ENTRY populates is defined like this:

 struct _ATL_INTMAP_ENTRY {     const IID* piid;       // The interface ID (IID)     DWORD dw;     _ATL_CREATORARGFUNC* pFunc; // NULL:end, 1:offset, n:ptr }; 

The structure holds a pointer to the interface ID, a DWORD offset of the interface implementation from the base class, and a final pFunc entry that is defined as the constant _ATL_SIMPLEMAPENTRY (1). This is the most common case—COM_INTERFACE_ENTRY is used to add a map entry. This macro calculates the inherited class offset using the _ComMapClass typedef and the ATL offsetofclass function. ComMapClass is just another alias for the object class name passed into the BEGIN_COM_MAP macro. The end of the array is marked if the pFunc member is 0. END_COM_MAP populates an _ATL_INTMAP_ENTRY for you that is all 0s. The pFunc argument serves multiple purposes. Instead of the constant 1 or 0, the pFunc argument can be a function pointer if the interface implementation isn't in the base class inheritance tree, which is the case with tear-off interfaces and aggregates. The pFunc argument can delegate to debugging code as well, as we'll see in the macro descriptions that follow. The DWORD member dw serves as an extra argument to pFunc in this case, not an offset. The content of dw depends on the function pointed to in pFunc. You can even create your own function that is called every time QueryInterface is called on a particular interface. This multiple use of structure members is somewhat confusing but efficient.

The first entry in the map is special in that it must be a simple map entry. The QueryInterface implementation relies on this simple map entry to ensure that COM identity is maintained for the object. The macros that define this type of entry and their descriptions are listed here:

  • COM_INTERFACE_ENTRY(x) The object derives directly from the implementation of this interface or from the interface itself. The interface's ID will be obtained using _ _uuidof(x).
  • COM_INTERFACE_ENTRY_IID(iid, x) This macro is the same as COM _INTERFACE _ENTRY except that the interface ID is explicitly specified. COM_INTERFACE_ENTRY_IID is useful in cases in which _ _uuidof isn't supported.
  • COM_INTERFACE_ENTRY2(x, x2) Use this macro for interfaces that appear in multiple base classes. Most often this occurs for IDispatch when inheriting from multiple dual interfaces. x is the interface that is entered into the array; x2 is the immediate base class the object derives from. _ _uuidof(x) is used to obtain the interface ID.
  • COM_INTERFACE_ENTRY2_IID(iid, x, x2) This macro is the same as COM_INTERFACE_ENTRY2, with the addition of the explicit interface ID parameter for x.

All of the preceding macros meet the simple interface requirement and can be used as the first map entry. Let's have a look at the remaining macros before we continue with our QueryInterface investigation.

  • COM_INTERFACE_ENTRY_BREAK(x) This macro causes a debug break in execution whenever QueryInterface is called for the interface x.
  • COM_INTERFACE_ENTRY_NOINTERFACE(x) This macro results in an E_NOINTERFACE return value whenever QueryInterface is called for the interface x.
  • COM_INTERFACE_ENTRY_FUNC(iid, dw, func) This macro allows you to set up any function to be the delegate for handling QueryInterface for interfaces of type iid. The function must have the following signature:

     HRESULT WINAPI func(void* pv, REFIID riid, LPVOID* ppv,     DWORD dw); 

    The final DWORD parameter is the same dw specified in the macro.
  • COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func) When you use this macro, querying for any interface other than IUnknown will delegate to func, regardless of the interface ID.
  • COM_INTERFACE_ENTRY_TEAR_OFF(iid, x) When you use this macro, ATL creates a new tear-off object by creating class x.
  • COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk) This macro delegates to a CComCachedTearOff derived class instance identified by punk to handle QueryInterface.
  • COM_INTERFACE_ENTRY_AGGREGATE(iid, punk) This macro delegates QueryInterface for iid to the inner unknown, punk.
  • COM_INTERFACE_ENTRY_AGGREGATE_BLIND(punk) If the interface hasn't been found in the COM map by the time this entry is evaluated, the query will be delegated to the inner unknown, regardless of iid.
  • COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid) If punk is not NULL, this macro works just like COM_INTERFACE_ENTRY_AGGREGATE. If punk is NULL, ATL creates an aggregate on the fly using clsid and delegates to its IUnknown.
  • COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk, clsid) This macro works the same way as AUTOAGGREGATE except that no interface ID comparison is made.
  • COM_INTERFACE_ENTRY_CHAIN(classname) This macro causes the COM map of an object's base class to be searched for any requested interface.

We look at each of these macros in detail in Chapter 8.

The order of macro placement in the map is important, especially for delegating and chaining. Top entries are evaluated first, followed by the entries below in the interface map.

At the beginning of this section, the COM map entered the picture when CComObject called _InternalQueryInterface. This is a static member declared by the BEGIN_COM _MAP macro. InternalQueryInterface delegates to CComObjectRootBase::InternalQueryInterface (which injects some debugging hooks that we'll see in the next section) and then delegates to AtlInternalQueryInterface. This is where the COM map is enumerated and QueryInterface is resolved or routed to any functions specified by the individual map macros. Before enumerating the map, AtlInternalQueryInterface checks to make sure that the first map entry is a simple map entry. As we said earlier, ATL uses this first entry for any queries for the IUnknown interface. The InlineIsEqualUnknown function just compares the interface ID to IID_IUnknown, as shown here:

 ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,     const _ATL_INTMAP_ENTRY* pEntries, REFIID iid,     void** ppvObject) {     ATLASSERT(pThis != NULL);     // First entry in the COM map should be a simple map entry     ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);     if(ppvObject == NULL)         return E_POINTER;     *ppvObject = NULL;     if(InlineIsEqualUnknown(iid)) // Use first interface     {         IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);         pUnk->AddRef();         *ppvObject = pUnk;         return S_OK;     } 

If IUnknown isn't what the client is looking for, the code falls through to a loop that enumerates the COM map entries. If the map entry doesn't specify an iid, it's considered a blind entry. If the entry is blind or its interface ID matches the one the client is looking for, one of two things happens:

  • If the entry has a pFunc of 1 (_ATL_SIMPLEMAPENTRY), the interface is part of the object itself and can be found at the offset specified in the dw member of the _ATL_INTMAP_ENTRY structure.
  • If the entry has a pFunc that points to a real function, ATL delegates to that function.

The loop code in AtlInternalQueryInterface is shown here:

 while (pEntries->pFunc != NULL) {     BOOL bBlind = (pEntries->piid == NULL);     if(bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))     {         if(pEntries->pFunc == _ATL_SIMPLEMAPENTRY) // Offset         {             ATLASSERT(!bBlind);             IUnknown* pUnk =                 (IUnknown*)((int)pThis+pEntries->dw);             pUnk->AddRef();             *ppvObject = pUnk;             return S_OK;         }         else // Actual function call         {             HRESULT hRes = pEntries->pFunc(pThis,                 iid, ppvObject, pEntries->dw);             if(hRes == S_OK || (!bBlind && FAILED(hRes)))                 return hRes;         }     }     pEntries++; } return E_NOINTERFACE; 

That's it for QueryInterface. For more information on tear-off interfaces and aggregation, look ahead to Chapter 8.



Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

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