previous page
next page

Table-Driven QueryInterface

The Raw Interface Map

ATL's implementation of QueryInterface is called InternalQueryInterface and is provided as a static member function of CComObjectRootBase (shown here with debugging extensions removed):

static HRESULT WINAPI                                         
CComObjectRootBase::InternalQueryInterface(                   
  void*                     pThis,                            
  const _ATL_INTMAP_ENTRY*  pEntries,                         
  REFIID                    iid,                              
  void**                    ppvObject)                        
{                                                             
    // First entry in the com map should be a simple map entry
    ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);        
    HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries, 
        iid, ppvObject);                                      
    return hRes;                                              
}                                                             

I show you the implementation of the internal function, AtlInternalQueryInterface, later. First, let's discuss the ATL_INTMAP_ENTRY structure, an array of which is passed to InternalQueryInterface:

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

Each entry provides a pointer to an interface identifier, a pointer to a function to retrieve the requested interface, and a user-defined parameter to pass to the function. Functions that fit into this table must have signatures defined by the _ATL_CREATORARGFUNC typedef:

typedef HRESULT (WINAPI _ATL_CREATORARGFUNC)(                 
    void* pv,        // Object's this pointer                 
    REFIID riid,     // IID of requested interface            
    LPVOID* ppv,     // Storage for returned interface pointer
    DWORD_PTR dw);   // dw from the interface map entry       

The job of the interface map function is to take the object's this pointer, the interface that the client is requesting, and the dw argument, and to return the appropriate interface pointer in the ppv argument. The function is free to do whatever it likes, within the laws of COM identity, to perform this magic. For example, the following function assumes that the dw member is the offset of the vptr from the this pointerthat is, this function assumes that we're using multiple inheritance (MI) to implement the interface:

HRESULT WINAPI _MI(void* pvThis, REFIID riid, LPVOID* ppv,
  DWORD dw) {
  *ppv = (BYTE*)pvThis + dw;
  reinterpret_cast<IUnknown*>(*ppv)->AddRef();
  return S_OK;
}

To fill in the _ATL_INTMAP_ENTRY for use with this function, we need to be able to calculate the offset of a vptr from the base, preferably at compile time. To help with this chore, ATL provides an interesting macro:

#define _ATL_PACKING 8                                        
#define offsetofclass(base, derived) \                        
   ((DWORD_PTR)(static_cast<base*>((derived*)_ATL_PACKING))- \
   _ATL_PACKING)                                              

The offsetofclass macro makes it look like we're asking the compiler to dereference a pointer with the value 8, which is not such a great value for a pointer.[1] Instead, we're asking the compiler to imagine a pointer to an object and to calculate the difference between that and a member inside that object, such as a vptr associated with a specific base class. The offsetofclass macro performs the same offset calculation the compiler does whenever it needs to perform a static_cast to a base class. Using offsetofclass enables us to fill an entry in an interface map like this:

[1] Microsoft didn't invent this particular oddity. The Standard C Runtime comes with a macro very much like offsetofclass, called offsetof.

class CBeachBall :
  public CComObjectRootEx<CComSingleThreadModel>,
  public ISphere,
  public IRollableObject,
  public IPlaything {
public:
const static _ATL_INTMAP_ENTRY* WINAPI
_GetEntries() {
  static const _ATL_INTMAP_ENTRY _entries[] = {
  { &IID_IUnknown, offsetofclass(ISphere, CBeachBall),
     _ATL_SIMPLEMAPENTRY },
  { &IID_ISphere, offsetofclass(ISphere, CBeachBall), _MI },
  { &IID_IRollableObject, offsetofclass(IRollableObject,
    CBeachBall), _MI },
  { &IID_IPlaything, offsetofclass(IPlaything, CBeachBall),
    _MI },
  };
return _entries;
};

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

Besides the population of the interface map, you can see a couple interesting things in this code snippet. First, the _InternalQueryInterface function calls _GetEntries to retrieve the static interface map and forwards it to the InternalQueryInterface static member function in CComObjectRootBase. CComObject et al requires the _InternalQueryInterface function to implement QueryInterface.

Second, notice that IUnknown is the initial entry in the list and uses _ATL_SIMPLEMAPENTRY instead of _MI. As discussed in Chapter 4, "Objects in ATL," the first entry is the one used for IUnknown and is required to be a simple entry. A simple entry is a special case that indicates that the interface is being exposed using multiple inheritance and that an offset is all that is needed to calculate the requested interface pointer. _ATL_SIMPLEMAPENTRY is a special value used in the pFunc field of the _ATL_INTMAP_ENTRY structure to indicate this case:

#define _ATL_SIMPLEMAPENTRY ((ATL::_ATL_CREATORARGFUNC*)1)

When AtlInternalQueryInterface encounters this special value, it knows how to perform the offset calculation just like the example function, _MI. Because _ATL_SIMPLEMAPENTRY completely replaces the need for a function that performs the offset calculation, ATL provides no interface map functions like _MI, although it provides others, as I discuss later.

Convenience Macros

You might enjoy writing the required _InternalQueryInterface and _GetEntries methods, as well as the GetUnknown method discussed in Chapter 4, but I do not. To write these functions and begin the static definition of the interface map, ATL provides BEGIN_COM_MAP:

#define BEGIN_COM_MAP(x) public: \                               
    typedef x _ComMapClass; \                                    
    ...                                                          
    IUnknown* _GetRawUnknown() { \                               
        ATLASSERT(_GetEntries()[0].pFunc == \                    
            _ATL_SIMPLEMAPENTRY); \                              
        return (IUnknown*)((INT_PTR)this+_GetEntries()->dw); \   
    } \                                                          
    _ATL_DECLARE_GET_UNKNOWN(x) \                                
    HRESULT _InternalQueryInterface(REFIID iid, \                
        void** ppvObject) { \                                    
        return InternalQueryInterface(this, _GetEntries(), \     
        iid, ppvObject); } \                                     
    const static ATL::_ATL_INTMAP_ENTRY* WINAPI _GetEntries() { \
    static const ATL::_ATL_INTMAP_ENTRY _entries[] = { \         
         DEBUG_QI_ENTRY(x)                                       

To zero-terminate the interface map and round out the _GetEntries implementation, ATL provides END_COM_MAP:

#define END_COM_MAP() \                                  
    __if_exists(_GetAttrEntries) {{NULL, \               
        (DWORD_PTR)_GetAttrEntries, _ChainAttr }, }\     
    {NULL, 0, 0}}; return _entries;} \                   
    virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0; \ 
    virtual ULONG STDMETHODCALLTYPE Release( void) = 0; \
    STDMETHOD(QueryInterface)(REFIID, void**) = 0;       

END_COM_MAP also provides another set of pure virtual member function definitions for QueryInterface, AddRef, and Release. This makes calls to IUnknown member functions unambiguous while calling them in the member functions of your ATL-based classes. We discuss the _GetAttrEntries function later in this chapter when we examine how ATL attributes can be used to declare an object's supported interfaces.

To populate each entry in the interface map, ATL provides a set of macros that begin with the COM_INTERFACE_ENTRY prefix. The simplest and most useful is COM_INTERFACE_ENTRY itself:

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

Notice the use of _ComMapClass as the name of the class associated with the static interface map. BEGIN_COM_MAP provides this type. The _ATL_IIDOF macro, on the other hand, is ATL's way of turning an interface type name into the corresponding GUID. Based on the presence or absence of the _ATL_NO_UUIDOF symbol, ATL either uses the VC++-specific __uuidof operator[2] or uses the C preprocessor's token-pasting operator:

[2] The __declspec(uuid()) and __uuidof operators are discussed in Chapter 4, "Objects in ATL."

#ifndef _ATL_NO_UUIDOF           
#define _ATL_IIDOF(x) __uuidof(x)
#else                            
#define _ATL_IIDOF(x) IID_##x    
#endif                           

Using these macros, the interface map can be defined much more simply than in the previous example:

class CBeachBall :
  public CComObjectRootEx<CBeachBall>,
  public ISphere,
  public IRollableObject,
  public IPlaything {
public:
BEGIN_COM_MAP(CBeachBall)
  COM_INTERFACE_ENTRY(ISphere)
  COM_INTERFACE_ENTRY(IRollableObject)
  COM_INTERFACE_ENTRY(IPlaything)
END_COM_MAP()
...
};

AtlInternalQueryInterface

Checking for IUnknown

InternalQueryInterface delegates to a global function, AtlInternalQueryInterface, to provide its implementation. Before walking the table of interface entries, AtlInternalQueryInterface checks the IID of the request. If IUnknown is requested, it pulls the first entry from the table and hands it back immediately, without walking the rest of the table. This is a welcome optimization, but it's more than that: It's absolutely necessary that IUnknown not be calculated by calling a function. Functions can fail and functions can return different values for the same interface identifier, both of which violate the laws of COM identity. The practical meaning for your classes is that the first entry must always be a simple one. Luckily, ATL is laden with assertions to this affect, so as long as you test your implementations at least once in debug mode before you ship them to your customers, you should be safe (on this note, anyway).

Walking the Table

At each entry in the table, a decision is made based on whether the piid member, a pointer to the interface identifier for that entry, is NULL. If it is not NULL, the IID of the entry is compared with the IID of the request. If a match is found, the function pFunc references are called and the result is returned to the client. If there is no match, the search advances to the next entry in the table.

On the other hand, if the piid member is NULL, no matter what the IID of the request is, the pFunc is called. If the result is S_OK, the result is returned to the client. Otherwise, the search continues with the next entry. This behavior is used for any of the COM_INTERFACE_ENTRY_XXX_BLIND macros, such as COM_INTERFACE_ENTRY_AGGREGATE_BLIND.

Implementation

The following is the implementation of AtlInternalQueryInterface:

ATLINLINE ATLAPI AtlInternalQueryInterface(                       
    void* pThis,                                                  
    const _ATL_INTMAP_ENTRY* pEntries,                            
    REFIID iid,                                                   
    void** ppvObject)                                             
{                                                                 
    ATLASSERT(pThis != NULL);                                     
    ATLASSERT(pEntries!= NULL);                                   
                                                                   
    if(pThis == NULL || pEntries == NULL)                         
        return E_INVALIDARG ;                                     
                                                                  
    // 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_PTR)pThis+pEntries->dw); 
         pUnk->AddRef();                                          
         *ppvObject = pUnk;                                       
         return S_OK;                                             
    }                                                             
    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_PTR)            
                    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;                                         
}                                                                 


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