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. 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:
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 or
uses the C preprocessor's token-pasting operator:
#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;
}
|