The ATL COM Map [Previous] [Next]

The ATL COM Map

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:

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.

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:

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.