ATL Cached Tear-Offs 

This article was contributed by Christopher Gordon. 

This article describes how to write COM classes that expose multiple, mutually-exclusive interfaces. The technique uses the ATL cached tear-off mechanism. By exclusivity I mean that although a COM class may implement many interfaces, a COM object that is derived from that class supports a subset of the interfaces. This is legal as long as the decision as to which interfaces to expose is made during the first call to QueryInterface. After the first call to QueryInterface the list of interfaces that the object supports must remain static; COM's object identity rules insist on this. This article will consider the case where at most one of many custom interfaces is chosen for exposure, although the ideas in the article could be extended to more complicated scenarios. 

Don Box (Essential COM, p. 164) describes the use of QueryInterface to exclude other interface. Box uses a code-driven implementation of QueryInterface to implement exclusion and elsewhere explains data-driven QueryInterface (Essential COM, p. 69). This article explains a similar solution but uses ATL cached tear-offs rather than coded alterations to QueryInterface. By providing exclusivity we prevent our clients from using an interface other than the one that was initially selected. By utilizing ATL's COM maps, we keep the flexibility afforded by data-driven QueryInterface. By using tear-offs we ensure that only the one vtbl is loaded into memory, thus avoiding vtbl bloat. Finally, by using only ATL mechanisms, we provide a technique that is easy to implement. 

Personally, I feel that the term "tear-off" is a little hazy. To me, the term evokes the thought of an interface's vtbls being pulled into memory when it is needed, used as an integral part of the object then pulled out of memory when no longer needed (interface-related data being "torn-off" of a COM object). Although the type of tear-off I'm describing is indeed kosher COM, neither of the two forms of the ATL tear-offs behave exactly like this. Non-cached tear-offs are indeed created and released as needed, but a new tear-off class is instantiated each time a query for the tear-off interface is made. These tear-offs do not really become an integral part of the object since they are typically used by only that client who requested them. Cached tear-offs are indeed created on demand and, once created are reused as integral parts of the COM object. However, once created, these tear-offs normally linger until the destruction of the COM object. In this case, perhaps "tear-in" is a more appropriate term. 

It is this latter form of tear-off that we'll focus on in this article. To understand the technique I'm proposing, we must first understand how a normal cached tear-off class operates. Let's take a look at the definition of a COM class that exposes two cached tear-off classes: 

class CIdentity;

class CTearOff1:

 public ITearOff1,

 public CComTearOffObjectBase

{

public:

 STDMETHOD(TearOff1Method)() { return S_OK; };

 BEGIN_COM_MAP(CTearOff1)

  COM_INTERFACE_ENTRY(ITearOff1)

 END_COM_MAP()

};

class CTearOff2:

 public ITearOff2,

 public CComTearOffObjectBase

{

public:

 STDMETHOD(TearOff2Method)() { return S_OK; };

 BEGIN_COM_MAP(CTearOff2)

  COM_INTERFACE_ENTRY(ITearOff2)

 END_COM_MAP()

};

class CIdentity:

 public CComObjectRootEx,

 public CComCoClass,

 public IIdentity

{

public:

 CIdentity() { m_pUnkTearOff1 = NULL; m_pUnkTearOff2 = NULL; };

 DECLARE_REGISTRY_RESOURCEID(IDR_IDENTITY)

 DECLARE_PROTECT_FINAL_CONSTRUCT()

 DECLARE_GET_CONTROLLING_UNKNOWN()

 BEGIN_COM_MAP(CIdentity)

  COM_INTERFACE_ENTRY(IIdentity)

  COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITearOff1, 

                                      CTearOff1, 

                                      m_pUnkTearOff1)

  COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITearOff2, 

                                      CTearOff2, 

                                      m_pUnkTearOff2)

 END_COM_MAP()

 IUnknown* m_pUnkTearOff1;

 IUnknown* m_pUnkTearOff2;

 void FinalRelease() 

 { 

  m_pUnkTearOff1->Release(); 

  m_pUnkTearOff2->Release(); 

 };

};

Notice that there are three declarations. Two classes implement the tear-off interfaces. Class CIdentity implements the COM object's identity. Besides providing the skeleton of the COM class, CIdentity provides the COM object's IUnknown pointer (recall that it is the IUnknown pointer that identifies a COM object). CIdentity contains two member variables - pointers to IUnknown. These are intended to point to the tear-off interfaces. 

Let's dig in and try to understand the CIdentity class, particularly its COM map. Below is the expansion of the map code (for clarity I've removed debug code and expanded the typedef): 

public:

 static HRESULT WINAPI _Cache(void* pv, 

                              REFIID iid, 

                              void** ppvObject, 

                              DWORD dw)

 {

  CIdentity * p = (CIdentity *)pv;

  p->Lock();

  HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);

  p->Unlock();

 

  return hRes;

 }

 IUnknown* GetUnknown()

 {

  return (IUnknown*)((int)this+_GetEntries()->dw);

 }

 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_IIdentity, 

    offsetofclass(IIdentity, CIdentity), 

    _ATL_SIMPLEMAPENTRY 

   },

   

   { 

    &IID_ITearOff1, 

    (DWORD)&_CComCacheData>,

    (DWORD)offsetof(CIdentity, pUnkTearOff1)>::data, 

    _Cache 

   },

  

   { 

    &IID_ITearOff2, 

    (DWORD)&_CComCacheData>,

    (DWORD)offsetof(CIdentity, pUnkTearOff2)>::data, 

    _Cache 

   },

   { 

    NULL, 

    0, 

    0 

   }

  };

 return _entries;

}

The _GetEntries singleton wraps an array of structures. Each structure contains interface information and indicates how to access a specific interface. 

The COM_INTERFACE_ENTRY() macro expands to become _entries' first element. This is a relatively simple case. There is an address to the IID, a DWORD and the constant _ATL_SIMPLEMAPENTRY that evaluates to 1. Because this last field's value of 1 indicates a simple entry, the DWORD is interpreted as the offset from the identity object's base address to the vptr that corresponds to the IID. 

The _entries' second and third elements correspond to our tear-offs. These are the expansions of the COM_INTERFACE_ENTRY_CACHED_TEAR_OFF() macros. Like the first, there is an address to the IID. However, rather than _ATL_SIMPLEMAPENTRY, the last field points to the function Cache. The DWORD field stores a pointer to an _ATL_CACHEDATA structure: 

struct _ATL_CACHEDATA

{

 DWORD dwOffsetVar;

 _ATL_CREATORFUNC* pFunc;

}

dwOffsetVar is the offset from the identity class to the pointer to the tear-off interface. Remember our two pointer to IUnknown member variables? These are their offsets. The function pointer pFunc points to the creator function for the tear-off class. An examination of the _GetEntries code above shows that the offset and the creator class are being bound together into an _ATL_CACHEDATA. 

All of this is directly employed by AtlInternalQueryInterface, ATL's implementation of map-based QueryInterface. ATL ultimately delegates all calls to QueryInterface to this function. 

ATLAPI AtlInternalQueryInterface(void* pThis, 

                                 const _ATL_INTMAP_ENTRY* pEntries, 

                                 REFIID iid, 

                                 void** ppvObject)

{

 // First entry in the com map should be a simple map entry

 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;

 }

 while (pEntries->pFunc != NULL)

 {

  BOOL bBlind = (pEntries->piid == NULL);

  if (bBlind 

  || InlineIsEqualGUID(*(pEntries->piid), iid))

  {

   if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset

   {

    _ASSERTE(!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;

}

After initial error checking, AtlInternalQueryInterface checks iid to see if a pointer to IUnknown has been requested. Next, the function walks through the Entries table. Notice that the macro END_COM_MAP(), expands to form an entry that is entirely composed of zeros. The while statement checks pFunc for a value of NULL since a NULL pFunc indicates the END_COM_MAP() and, therefore, the last element in _entries. 

If the current entry indicates an _ATL_SIMPLEMAPENTRY, the offset is used to determine the desired vptr. 

If an interface entry is not an _ATL_SIMPLEMAPENTRY, the pFunc will be called. In the case of cached tear-offs, this means a call to _Cache. Notice that _Cache is defined in the COM map macro. Notice too that _Cache in turn makes a call to CComObjectRootBase::_Cache. 

static HRESULT WINAPI

CComObjectRootBase::_Cache(void* pv, 

                           REFIID iid, 

                           void** ppvObject, 

                           DWORD dw)

{

 HRESULT hRes = E_NOINTERFACE;

 _ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw;

 IUnknown** pp = (IUnknown**)((DWORD)pv + pcd->dwOffsetVar);

 if (*pp == NULL) 

  hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp);

 if (*pp != NULL) 

  hRes = *pp->QueryInterface(iid, ppvObject);

 return hRes;

}

_Cache takes as it's parameters the identity class's "this" pointer, a pointer to the IID of the interface, the IUnknown pointer to the interface and, finally, the map entry's DWORD value, respectively. 

Since the interface is a tear-off, dw must be an _ATL_CACHEDATA and so _Cache appropriately casts it as such. Next, _Cache adds the offset member of dw to the identity's "this" pointer and casts the data in the resulting address to a pointer to a pointer to IUnkown. This is good and proper when you remember that the COM map defined _ATL_CACHEDATA's dwOffsetVar member as the offset from the identity class to one of the member variables intended to point to the tear-off interfaces. Here _Cache retrieves the address of that data member, casts it to its original form and calls it pp. Next, _Cache examines pp to determine if it points to a NULL pointer. If it does, _Cache calls the tear-off's creator function in _ATL_CACHEDATA and sets *pp to point to the tear-off. If *pp is not NULL, this is indication that the requested tear-off was previously "torn-in" and is just hanging around. Finally, _Cache again examines the pp pointer to determine that it does not point to a NULL pointer. Unlike the first check, this check is standard C++ error checking. Under normal operation the location should not be NULL since either *pp was set to point to the tear-off in a previous call or the creator created the object in the previous line and placed its address in *pp. Assuming no major errors and *pp is not null, _Cache calls QueryInterface on the tear-off object and requests the desired interface. You may have noticed that each tear-off class has its own COM map with a single, simple entry. This QueryInterface searches the tear-off's COM map for the interface. The QueryInterface is non-delegating, a concept related to aggregation but that is beyond the scope of this article. You only need to know that if the interface exists in the tear-off's COM map, it will be returned, otherwise query interface will fail. 

Let's recap, the upshot of _Cache is that if a tear-off class was not previously instantiated, _Cache will see to it that it is created and that its corresponding member variable points to it. If a tear-off class has already been instantiated, its corresponding member variable already points to it. Either way, the member variable will be used to call QueryInterface on the tear-off class to retrieve the desired tear-off interface. 

There's something that we have yet to describe. The tear-off needs to be able to point back to the class that implements COM object identity. If a client who holds a tear-off interface desires IUnknown or some other interface the COM object supports, it would be very rude (not to mention illegal) if our object didn't supply it. A pointer back to the identity class provides the ability to direct a client's QueryInterface calls on the tear-off interface back to the identity class. Unlike the _Cache's call to QueryInterface this call is delegating. Again we can't get into all of the details of aggregation. Just be aware that a client who calls QueryInterface on a tear-off interface is directed back to the identity object to search the COM map there rather than merely searching the tear-off's COM map. Otherwise, the client would be trapped in the tear-off's COM map, never able to get pointers to interfaces from CIdentity. 

The base class of the tear-offs CComTearOffObjectBase provides the facilities to link back to the identity class. 

template 

class CComTearOffObjectBase : public CComObjectRootEx

{

public:

 typedef Owner _OwnerClass;

 CComObject* m_pOwner;

 CComTearOffObjectBase() { m_pOwner = NULL; };

};

The class contains m_pOwner, a pointer back to the identity class. This is what will be used to link back to the identity class. It can also be used to access data members of the identity class. 

How is the m_pOwner used in reference to QueryInterface? The expansion of the COM map indicates that the tear-off class will be wrapped inside of the CComCachedTearOffObject template. Although this templatized class is complicated, we can still understand the highlights. 

template 

class CComCachedTearOffObject : public IUnknown, public 

CComObjectRootEx

{

public:

 typedef contained _BaseClass;

 CComCachedTearOffObject(void* pv) : 

 m_contained(((contained::_OwnerClass*)pv)->GetControllingUnknown())

 {

  m_contained.m_pOwner = 

   reinterpret_cast*>(pv);

 }

 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)

 {

  HRESULT hRes = S_OK;

  if (InlineIsEqualUnknown(iid))

  {

   *ppvObject = (void*)(IUnknown*)this;

   AddRef();

  }

  else

   hRes = m_contained._InternalQueryInterface(iid, ppvObject);

  return hRes;

 }

 CComContainedObject m_contained;

};

The template receives the tear-off class as its "contained" parameter. At the bottom of CComCachedTearOffObject we see that it passes contained (the tear-off class) to the CComContainedObject template to form the data type for m_contained. Notice that the constructor casts pv to a pointer CComObject. This is a pointer to the identity object. The constructor also knows about the tear-off's m_pOwner, and sets it to point to pv. The m_pOwner variable now points to the identity class so that communication back to it is possible. 

We've seen how ATL creates cached tear-offs as well as how it maintains the links to these tear-offs. Obviously, cached tear-offs are preserved after they are created. This is the reason for the need to release the pointers to the tear-offs in the final release of the identity class. Had these been non-cached tear-offs they would still hold links back to the identity class, but the identity class would not preserved links back to them. 

So what about exclusivity? It turns out that this is simple enough to handle. We desire an object that supports one and only one tear-off interface so, rather than having a member variable for each interface the COM class implements, we can get by with one member variable for our one supported interface. It will point to the one lucky interface that the client chooses to support upon the initial call to QueryInterface. The declaration of CIdentity requires minimal changes: 

class CIdentity:

 public CComObjectRootEx,

 public CComCoClass,

 public IIdentity

{

public:

 CIdentity() { m_pUnkTearOff = NULL; };

 DECLARE_REGISTRY_RESOURCEID(IDR_IDENTITY)

 DECLARE_PROTECT_FINAL_CONSTRUCT()

 DECLARE_GET_CONTROLLING_UNKNOWN()

 BEGIN_COM_MAP(CIdentity)

  COM_INTERFACE_ENTRY(IIdentity)

  COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITearOff1, 

                                      CTearOff1, 

                                      m_pUnkTearOff)

  COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITearOff2, 

                                      CTearOff2, 

                                      m_pUnkTearOff)

 END_COM_MAP()

 IUnknown* m_pUnkTearOff;

 void FinalRelease() { m_pUnkTearOff->Release(); };

};

With the exception of the interface pointer member variables, the resulting _entries array will be exactly the same. 

Let's consider what happens upon the first query for an exclusive interface. Suppose the client queries for one of the two tear-off interfaces, let's suppose it happens to query for ITearOff2. As previously described, the AtlInternalQueryInterface will eventually find the COM map entry that matches the desired IID. It will examine the identity object's pointer to IUnkown. Since the COM object has just been created, neither of the two tear-offs has been instantiated and, therefore, the pointer member variable will be NULL. Consequently, _Cache will call the creator function for CTearOff2 then query the tear-off class for ITearOff2. Assuming no catastrophic failure, the call should succeed. m_pUnkTearOff will be set to point to the tear-off class and the client will get back the requested pointer to ITearOff2. 

Now, let's consider what happens when the client ignores our documentation and queries for ITearOff1. Again, the AtlInternalQueryInterface will examine ITearOff1's COM map entry. Since all of the interfaces in the COM map employ the same IUnknown pointer member variable (m_pUnkTearOff), all of the tear-off entries in _entries have identical offset. AtlInternalQueryInterface will certainly find m_pUnkTearOff to be non-NULL since it was previously set to point to ITearOff2. Since the pointer is non-NULL, _Cache won't bother calling any creator function, it will simply make the call to QueryInterface. But, m_pUnkTearOff points to ITearOff2, this means querying the CTearOff2 COM map for its ITearOff1 interface. Clearly, this will fail since the COM map belonging to CTearOff2 contains no entry for ITearOff1. This failure is a good thing since our COM object is designed so that once the client selected ITearOff2, all other interfaces were excluded. 

This technique provides exclusivity. It also results in a smaller memory footprint due to the reduction of vtbl bloat afforded by tear-offs. The combination of tear-off and exclusivity means that for all the mutually exclusive interfaces supported by the COM class at most one of their vtbls will be loaded into memory. Should a second client gain hold of a pointer to the object's IUnknown interface it can use QueryInterface to determine which interface the creating client selected (of course, with the caveat that if the creating client had not selected an exclusive interface (perhaps they merely held onto IUnknown), the second client's first call to QueryInterface would make the selection.) Thus, QueryInterface can determine which persona the object has taken. 

I should mention one difficulty you may encounter when a Visual Basic client attempts to use this type of COM object. Recall that IDL provides you the ability to indicate which, if any, of your interfaces is the default interface. The default interface usually is that interface that best represents the essential nature of the COM Class. An object declared in Visual Basic automatically assumes the default interface. It isn't that Visual Basic may use default interfaces, it will use them. If none are explicitly indicated, Visual Basic forces the first interface listed in the CoClass definition to be the default (Essential COM, p. 142-143). You can probably see what's coming. If all of the interfaces listed in the IDL's CoClass definition are intended to be mutually exclusive, Visual Basic, not your client, will select which interface gets supported. This is because upon object creation, Visual Basic will get a default interface one way or the other. 

There are a couple of ways around this. If not all of your interfaces are mutually exclusive and you desire the default interface to be one of your non-mutually exclusive interface, no problem. Simply indicate it as the default in IDL. However, if your intent is that all interfaces be mutually exclusive, simply include IUnknown in the list of interfaces in your CoClasses definition and mark it as the default. This will prevent any premature selection of an exclusive interface.