Interface Map
Chaining
C++ programmers are
accustomed to using inheritance of implementation for reuse. For
example, we reuse the implementation provided in
CComObjectRootEx as well as the various ATL implementation
classes (such as IDispatchImpl) tHRough inheritance. For
each implementation class used, one or more corresponding entries
must be made in the interface map. However, what about deriving
from a class that already provides an interface map? For
example
class CBigBeachBall :
public CBeachBall,
public IBigObject {
public:
BEGIN_COM_MAP(CBigBeachBall)
COM_INTERFACE_ENTRY(IBigObject)
// All entries from CBeachBall base?
END_COM_MAP()
...
};
COM_INTERFACE_ENTRY_CHAIN
When inheriting from a base class that provides
its own interface map, we want to avoid duplicating all the entries
in the deriving class's interface map. The reason is maintenance.
If the base class decides to change how it supports an interface or
wants to add or remove support for an interface, we've got to
change the deriving classes, too. It would be much nicer to
"inherit" the interface map along with the interface
implementations. That's what COM_INTERFACE_ENTRY_CHAIN
does:
#define COM_INTERFACE_ENTRY_CHAIN(classname) \
{ NULL, (DWORD_PTR)&ATL::_CComChainData<classname, \
_ComMapClass>::data, _Chain },
The _CComChainData template simply
fills the dw member of the interface entry with a pointer
to the base class's interface map so that the _Chain
function can walk that list when evaluating a query request:
static HRESULT WINAPI
CComObjectRootBase::_Chain(void* pv, REFIID iid,
void** ppvObject, DWORD dw) {
_ATL_CHAINDATA* pcd = (_ATL_CHAINDATA*)dw;
void* p = (void*)((DWORD_PTR)pv + pcd->dwOffset);
return InternalQueryInterface(p, pcd->pFunc(), iid, ppvObject);
}
If the _Chain function returns a
failurefor example, if the base class doesn't support the requested
interfacethe search continues with the next entry in the table:
class CBigBadBeachBall :
public CBeachBall,
public IBigObject,
public IBadObject {
public:
BEGIN_COM_MAP(CBigBadBeachBall)
COM_INTERFACE_ENTRY(IBigObject)
COM_INTERFACE_ENTRY_CHAIN(CBeachBall)
COM_INTERFACE_ENTRY(IBadObject)
END_COM_MAP()
...
};
It might seem natural to put the chaining
entries first in the interface map. However, remember that the
first entry must be a simple entry, so you must put at least one of
the derived class's interfaces first. If the derived class has no
additional interfaces, use IUnknown as the first
entry:
class CBetterBeachBall :
public CBeachBall {
public:
BEGIN_COM_MAP(CBetterBeachBall)
COM_INTERFACE_ENTRY(IUnknown)
COM_INTERFACE_ENTRY_CHAIN(CBeachBall)
END_COM_MAP()
...
};
|