Chapter 6. Interface Maps
Chapter 4, “Objects in ATL,” discussed how ATL
implements IUnknown
, but it covered only AddRef
and Release
completely. This chapter takes a look first at
the requirements that COM makes on an object’s implementation of QueryInterface
and then at how ATL supports those
requirements while still providing flexibility and
extensibility.
Recall: COM Identity
From a client perspective, the rules of AddRef
and Release
are fairly stringent. Unless
the client is careful about their use, objects can go away before
expected or can stay around too long. However, the object is
allowed to implement AddRef
and Release
in any
number of ways, depending on how it wants to manage its own
lifetime – for example, as a heap-based, stack-based, or cached
object.
On the other hand, QueryInterface
is
easy to get right on the client side. Any client can ask an object
if it supports any other functionality with a simple call. However,
clients expect certain relationships between the interfaces on a
COM object. These expectations form the laws of COM identity.
The Laws of COM Identity
The laws of COM identity say the following
things about how an object must expose its interfaces via QueryInterface
:
A client must be capable of getting directly to any interface implemented by the object via
QueryInterface
.An object’s interfaces must be static throughout its lifetime.
QueryInterface
forIUnknown
must always succeed and must always return the same pointer value.
Direct Access to All Interfaces
The COM Specification states that an
implementation of QueryInterface
must be “reflexive,
symmetric, and transitive.” This means that, given an interface, a
client must be capable of using an interface to get
directly to any interface implemented on the object, including the
interface the client is using to perform the query. These
relationships are mandated to maintain an object’s identity in the
face of multiple references to the same object. If these
relationships are not upheld, a client could find itself with some
code that doesn’t work just because it asked for the interfaces in
the wrong order. With a properly implemented QueryInterface
, query order does not matter.
Static Types
Each object can decide for itself whether it
wants to expose an interface via QueryInterface
,
regardless of the class to which it belongs. However, after it has
been asked and has answered either “Yes, I support that interface”
or “No, I don’t support that interface,” it must stick to that
answer. The reason for this is simple: After an object answers the
query, it may never be asked again. For example, a client can pass
a resultant interface pointer to another client, which never has to
ask the object at all.
The potential for clients “talking among
themselves” means that an object cannot use QueryInterface
to make client-specific decisions – for example, those based on
security constraints. The object also cannot use QueryInterface
to make context decisions that could change
during the life of an object, such as time of day. If a client
caches an interface pointer returned when the context is favorable,
it might not ask again when the context has changed.
An Object’s Apartment-Specific Identifier
The remoting layer of COM uses the pointer
returned when querying for IUnknown
as an object’s unique
identifier in that apartment. Clients can also compare IUnknown*
s as an identity test:
1 bool AreEqualObjects(IUnknown* punk1, IUnknown* punk2) {
2 if( punk1 == null && punk2 == null ) return true;
3 if( !punk1 || !punk2 ) return false;
4 IUnknown* punka = 0; punk1->QueryInterface(IID_IUnknown,
5 (void**)&punka);
6 IUnknown* punkb = 0; punk2->QueryInterface(IID_IUnknown,
7 (void**)&punkb);
8 bool b = (punka == punkb);
9 punka->Release(); punkb->Release();
10 return b;
11 }
In fact, the ATL smart pointer classes have a
method called IsEqualObject
for performing just this
comparison:
1 STDMETHODIMP CBall::SetPlaySurface(IRollSurface* prsNew) {
2 if( m_sprs.IsEqualObject(prsNew) ) return S_OK;
3 ...
4 }
However, although COM dictates that the pointer
value of IUnknown
must always be the same, it places no
such restrictions on any other interface. This particular loophole
leads to such techniques as tear-off interfaces, discussed further
in this chapter.
Nothing Else
As long as these three laws are upheld, an
implementation of QueryInterface
can be developed using
scenes from your most vivid fever dreams. Frankly, I doubt you’ll
be able to come up with any techniques wackier than those already
known, as I present during the rest of this chapter. However, if
you do, ATL’s implementation of QueryInterface
is fully
extensible, as you’ll see.
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):
1 static HRESULT WINAPI
2 CComObjectRootBase::InternalQueryInterface(
3 void* pThis,
4 const _ATL_INTMAP_ENTRY* pEntries,
5 REFIID iid,
6 void** ppvObject)
7 {
8 // First entry in the com map should be a simple map entry
9 ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
10 HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries,
11 iid, ppvObject);
12 return hRes;
13 }
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
:
1 struct _ATL_INTMAP_ENTRY {
2 const IID* piid; // the interface id (IID)
3 DWORD_PTR dw;
4 _ATL_CREATORARGFUNC* pFunc; //NULL:end, 1:offset, n:ptr
5 };
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:
1 typedef HRESULT (WINAPI _ATL_CREATORARGFUNC)(
2 void* pv, // Object's this pointer
3 REFIID riid, // IID of requested interface
4 LPVOID* ppv, // Storage for returned interface pointer
5 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
pointer – that is, this function
assumes that we’re using multiple inheritance (MI) to implement the
interface:
1 HRESULT WINAPI _MI(void* pvThis, REFIID riid, LPVOID* ppv, DWORD dw) {
2 *ppv = (BYTE*)pvThis + dw;
3 reinterpret_cast<IUnknown*>(*ppv)->AddRef();
4 return S_OK;
5 }
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:
1 #define _ATL_PACKING 8
2 #define offsetofclass(base, derived) \
3 ((DWORD_PTR)(static_cast<base*>((derived*)_ATL_PACKING))- \
4 _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 class CBeachBall :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything {
6 public:
7 const static _ATL_INTMAP_ENTRY* WINAPI
8 _GetEntries() {
9 static const _ATL_INTMAP_ENTRY _entries[] = {
10 { &IID_IUnknown, offsetofclass(ISphere, CBeachBall),
11 _ATL_SIMPLEMAPENTRY },
12 { &IID_ISphere, offsetofclass(ISphere, CBeachBall), _MI },
13 { &IID_IRollableObject, offsetofclass(IRollableObject,
14 CBeachBall), _MI },
15 { &IID_IPlaything, offsetofclass(IPlaything, CBeachBall),
16 _MI },
17 };
18 return _entries;
19 };
20
21 HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject)
22 {
23 return InternalQueryInterface(this, _GetEntries(), iid,
24 ppvObject);
25 }
26 ...
27 };
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:
1 #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
:
1 #define BEGIN_COM_MAP(x) public: \
2 typedef x _ComMapClass; \
3 ...
4 IUnknown* _GetRawUnknown() { \
5 ATLASSERT(_GetEntries()[0].pFunc == \
6 _ATL_SIMPLEMAPENTRY); \
7 return (IUnknown*)((INT_PTR)this+_GetEntries()->dw); \
8 } \
9 _ATL_DECLARE_GET_UNKNOWN(x) \
10 HRESULT _InternalQueryInterface(REFIID iid, \
11 void** ppvObject) { \
12 return InternalQueryInterface(this, _GetEntries(), \
13 iid, ppvObject); } \
14 const static ATL::_ATL_INTMAP_ENTRY* WINAPI _GetEntries() { \
15 static const ATL::_ATL_INTMAP_ENTRY _entries[] = { \
16 DEBUG_QI_ENTRY(x)
To zero-terminate the interface map and round
out the _GetEntries
implementation, ATL provides END_COM_MAP:
1 #define END_COM_MAP() \
2 __if_exists(_GetAttrEntries) {{NULL, \
3 (DWORD_PTR)_GetAttrEntries, _ChainAttr }, }\
4 {NULL, 0, 0}}; return _entries;} \
5 virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0; \
6 virtual ULONG STDMETHODCALLTYPE Release( void) = 0; \
7 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:
1 #define COM_INTERFACE_ENTRY(x)\
2 {&_ATL_IIDOF(x), \
3 offsetofclass(x, _ComMapClass), \
4 _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:
The __declspec(uuid())
and __uuidof
operators are
discussed in Chapter4, “Objects in ATL.”
1 #ifndef _ATL_NO_UUIDOF
2 #define_ATL_IIDOF(x)__uuidof(x)
3 #else
4 #define_ATL_IIDOF(x)IID_##x
5 #endif
Using these macros, the interface map can be defined much more simply than in the previous example:
1 class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything {
6 public:
7 BEGIN_COM_MAP(CBeachBall)
8 COM_INTERFACE_ENTRY(ISphere)
9 COM_INTERFACE_ENTRY(IRollableObject)
10 COM_INTERFACE_ENTRY(IPlaything)
11 END_COM_MAP()
12 ...
13 };
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
:
1 ATLINLINE ATLAPI AtlInternalQueryInterface(
2 void* pThis,
3 const _ATL_INTMAP_ENTRY* pEntries,
4 REFIID iid,
5 void** ppvObject)
6 {
7 ATLASSERT(pThis != NULL);
8 ATLASSERT(pEntries!= NULL);
9
10 if(pThis == NULL || pEntries == NULL)
11 return E_INVALIDARG ;
12
13 // First entry in the com map should be a simple map entry
14 ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
15 if (ppvObject == NULL)
16 return E_POINTER;
17 *ppvObject = NULL;
18 if (InlineIsEqualUnknown(iid)) { // use first interface
19 IUnknown* pUnk=(IUnknown*)((INT_PTR)pThis+pEntries->dw);
20 pUnk->AddRef();
21 *ppvObject = pUnk;
22 return S_OK;
23 }
24 while (pEntries->pFunc != NULL) {
25 BOOL bBlind = (pEntries->piid == NULL);
26 if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) {
27 if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) { //offset
28 ATLASSERT(!bBlind);
29 IUnknown* pUnk = (IUnknown*)((INT_PTR)
30 pThis+pEntries->dw);
31 pUnk->AddRef();
32 *ppvObject = pUnk;
33 return S_OK;
34 }
35 else { //actual function call
36 HRESULT hRes = pEntries->pFunc(pThis,
37 iid, ppvObject, pEntries->dw);
38 if (hRes == S_OK || (!bBlind && FAILED(hRes)))
39 return hRes;
40 }
41 }
42 pEntries++;
43 }
44 return E_NOINTERFACE;
45 }
Multiple Inheritance
To support multiple
inheritance (MI) of interfaces, ATL provides four separate
interface entry macros. Two are for straight casts and two are for branching casts. A straight cast is a static_cast
that the compiler needs no extra information
to perform – that is, there are no ambiguities. On the other hand, a
branching cast is used when a class has several base classes that
all derive from the same base class themselves. Because a straight
cast to the common base class would be ambiguous, the compiler
needs the inheritance branch to follow to resolve the
ambiguity.
Straight Casting
COM_INTERFACE_ENTRY and COM_INTERFACE_ENTRY_IID
As I mentioned, COM_INTERFACE_ENTRY
is
the one you’ll use most of the time. Its close cousin is COM_INTERFACE_ENTRY_IID
:
1#define COM_INTERFACE_ENTRY_IID(iid, x) \
2 { &iid, offsetofclass(x, _ComMapClass), _ATL_SIMPLEMAPENTRY},
This macro enables you to specify the IID separately from the name of the interface. The classic use of this macro is to avoid ambiguity. Imagine that you’ve got two interfaces that derive from the same base interface:
1interface IGlobe : ISphere {};
2interface IPlanet : ISphere {};
If you’ve got a class that derives from both of
these interfaces, the compiler won’t know which base class to cast
to if you use COM_INTERFACE_ENTRY
for ISphere
:
1class CDesktopGlobe :
2 public CComObjectRootEx<CDesktopGlobe>,
3 public IGlobe,
4 public IPlanet {
5public:
6 ...
7BEGIN_COM_MAP(CDesktopGlobe)
8 COM_INTERFACE_ENTRY(ISphere) // ambiguous
9 COM_INTERFACE_ENTRY(IGlobe)
10 COM_INTERFACE_ENTRY(IPlanet)
11END_COM_MAP()
12 // ISphere methods
13 ...
14 // IGlobe methods
15 ...
16 // IPlanet methods
17 ...
18};
The problem is more easily seen when you look at the inheritance hierarchy in Figure 6.1.
Figure 6.1. CDesktopGlobe inheritance hierarchy

Figure6.1 shows two interfaces that CDesktopGlobe
has
inherited from more than once, IUnknown
and ISphere
. IUnknown
is not a problem because ATL
handles it specially by choosing the first entry in the interface
map (as discussed earlier). ISphere
is a problem, though,
because there are two of them, IGlobe
and IPlanet
. Each base interface has a separate vptr
that points to a separate vtbl
. Even though we’ve got a
shared implementation of all the methods of ISphere
and,
therefore, duplicate entries in both the IGlobe
and the IPlanet
vtbls
, the compiler needs us to pick one. COM_INTERFACE_ENTRY_IID
enables us to resolve this
ambiguity:
1class CDesktopGlobe :
2 public CComObjectRootEx<CDesktopGlobe>,
3 public IGlobe,
4 public IPlanet {
5public:
6...
7BEGIN_COM_MAP(CDesktopGlobe)
8 COM_INTERFACE_ENTRY_IID(IID_ISphere, IGlobe) // unambiguous
9 COM_INTERFACE_ENTRY(IGlobe)
10 COM_INTERFACE_ENTRY(IPlanet)
11END_COM_MAP()
12...
13};
In this case, because we
have shared implementations of the ISphere
methods in our
implementation of IGlobe
and IPlanet
, it doesn’t
really matter which one we hand out. Sometimes it matters very
much. COM_INTERFACE_ENTRY_IID
is often used when exposing
multiple dual interfaces, each of which derives from IDispatch
. Using IDispatchImpl
, we provide base
class implementations of IDispatch
that are different
based on the dual interface we’re implementing. In fact, any time
you’ve got multiple implementations of the same interface in the
base classes, you must decide which implementation is the
“default.” Imagine another example of a base class interface
implementation that has nothing to do with scripting:
1template <typename Base> class ISphereImpl : public Base {...};
Using ISphereImpl
looks like this:
1class CDesktopGlobe :
2 public CComObjectRootEx<CDesktopGlobe>,
3 public ISphereImpl<IGlobe>,
4 public ISphereImpl<IPlanet> {
5public:
6...
7BEGIN_COM_MAP(CDesktopGlobe)
8 COM_INTERFACE_ENTRY_IID(IID_ISphere, IGlobe) // Default ISphere
9 COM_INTERFACE_ENTRY(IGlobe)
10 COM_INTERFACE_ENTRY(IPlanet)
11END_COM_MAP()
12...
13};
Here’s the problem: If the client queries for IGlobe
(or ISphere
) and calls ISphere
methods, it gets different behavior than if it were to query for IPlanet
and call ISphere
methods. Now the client
has just the kind of order-of-query problem that the laws of COM
identity were built to prohibit. Multiple implementations of the same base interface
clearly violate the spirit, if not the letter, of the laws of COM
identity.
Branch Casting
COM_INTERFACE_ENTRY2 and COM_INTERFACE_ENTRY2_IID
Both COM_INTERFACE_ENTRY2
and COM_INTERFACE_ENTRY2_IID
are simple entries meant for use
with MI:
1 #define COM_INTERFACE_ENTRY2(x, x2)\
2 { &_ATL_IIDOF(x),\
3 reinterpret_cast<DWORD_PTR>( \
4 static_cast<x*>( \
5 static_cast<x2*>( \
6 reinterpret_cast<_ComMapClass*>(8))))-8, \
7 _ATL_SIMPLEMAPENTRY},
8
9 #define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\
10 { &iid,\
11 reinterpret_cast<DWORD_PTR>( \
12 static_cast<x*>( \
13 static_cast<x2*>( \
14 reinterpret_cast<_ComMapClass*>(8))))-8, \
15 _ATL_SIMPLEMAPENTRY},
COM_INTERFACE_ENTRY2
is much like COM_INTERFACE_ENTRY_IID
because it enables you to resolve
the problem of multiple bases:
1class CDesktopGlobe :
2 public CComObjectRootEx<CDesktopGlobe>,
3 public IGlobe,
4 public IPlanet {
5public:
6...
7BEGIN_COM_MAP(CDesktopGlobe)
8 COM_INTERFACE_ENTRY2(ISphere, IGlobe) // Use the IGlobal branch
9 COM_INTERFACE_ENTRY(IGlobe)
10 COM_INTERFACE_ENTRY(IPlanet)
11END_COM_MAP()
12...
13};
This macro performs its magic by enabling you to
specify two things, the interface to expose (such as ISphere
) and the branch of the inheritance hierarchy tofollow to get to the implementation of that
interface (such as IGlobe
). This macro is slightly
different from COM_INTERFACE_ENTRY_IID
, in that the
interface is specified by name instead of by IID. If you want to be
very explicit about both, use COM_INTERFACE_ENTRY2_IID
:
1class CDesktopGlobe :
2 public CComObjectRootEx<CDesktopGlobe>,
3 public IGlobe,
4 public IPlanet {
5public:
6...
7BEGIN_COM_MAP(CDesktopGlobe)
8 COM_INTERFACE_ENTRY2_IID(&IID_ISphere, ISphere, IGlobe)
9 COM_INTERFACE_ENTRY(IGlobe)
10 COM_INTERFACE_ENTRY(IPlanet)
11END_COM_MAP()
12...
13};
COM_INTERFACE_ENTRY2[_IID]
provides no
extra functionality beyond what COM_INTERFACE_ENTRY[_IID]
provides, so I tend to always use the latter.
Handling Name Conflicts
One of the problems with MI is name collisions. Imagine the following interfaces:
1interface ICowboy : IUnknown {
2 HRESULT Draw();
3};
4
5interface IArtist : IUnknown {
6 HRESULT Draw();
7};
Because both Draw
methods have the same
signature, using straight MI requires a single shared
implementation:
1// Ace Powell was a cowboy/artist who lived in the western US
2// from 1912 to his death in 1978. I'd like to thank Tim Ewald
3// for this fabulous example, which I have used to death
4// for years.
5class CAcePowell :
6 public CComObjectRootEx<CComSingleThreadModel>,
7 public ICowboy,
8 public IArtist
9{
10public:
11BEGIN_COM_MAP(CAcePowell)
12 COM_INTERFACE_ENTRY(ICowboy)
13 COM_INTERFACE_ENTRY(IArtist)
14END_COM_MAP()
15...
16 STDMETHODIMP Draw() { /* Act as a cowboy or an artist? */ }
17};
The implied meaning of Draw
is very
different for an artist than it is for a cowboy, so we’d like to be
able to provide two Draw
implementations. We can deal with
this predicament in a couple ways. The first solution uses a
Microsoft-specific extension to the C++ language that allows a very
intuitive syntax for disambiguating the Draw
implementation. It’s employed as follows:
1class CAcePowell :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public ICowboy,
4 public IArtist {
5public:
6BEGIN_COM_MAP(CAcePowell)
7 COM_INTERFACE_ENTRY(ICowboy)
8 COM_INTERFACE_ENTRY(IArtist)
9END_COM_MAP()
10...
11 STDMETHODIMP IArtist::Draw() {
12 /* Draw like an artist */
13 return S_OK;
14 }
15
16 STDMETHODIMP ICowboy::Draw() {
17 /* Draw like a cowboy */
18 return S_OK;
19 }
20};
By decorating the method with the name of the
interface, the compiler can figure out which Draw
method
you are implementing. However, there is an important limitation
with this technique imposed by what I consider a bug in the
compiler: You must place the body of these methods in the class
declaration in the header file. If you try to just put a
declaration in the header file and put the body in the CPP file, it
won’t compile.
This syntax is not Standard C++, so don’t expect to use this with non-Microsoft compilers (not that much of the rest of this book would be useful on non-Microsoft compilers anyway). If you’re implementing clashing methods from multiple interfaces, you might want to turn to alternative technique to address the problem of name collision. I call this technique, long known to the C++ community, forwarding shims. [3]
Tim Ewald showed me this technique originally, and Jim Springfield made me see its relevance to ATL.
Forwarding shims rely upon the fact that although we can’t distinguish methods in the most derived class, we can certainly distinguish the methods in individual base classes:
1struct _IArtist : public IArtist {
2 STDMETHODIMP Draw() { return ArtistDraw(); }
3 STDMETHOD(ArtistDraw)() =0;
4};
5
6struct _ICowboy : public ICowboy {
7 STDMETHODIMP Draw() { return CowboyDraw(); }
8 STDMETHOD(CowboyDraw)() =0;
9};
Both _IArtist
and _ICowboy
are
shim classes that implement the method with the conflicting name
and forward to another pure virtual member function with a unique
name. Because both shims derive from the interface in question, the
interfaces IArtist
and ICowboy
can still appear
in the interface map without difficulty:
1class CAcePowell :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public _ICowboy,
4 public _IArtist {
5public:
6BEGIN_COM_MAP(CAcePowell)
7 COM_INTERFACE_ENTRY(ICowboy)
8 COM_INTERFACE_ENTRY(IArtist)
9END_COM_MAP()
10...
11 STDMETHODIMP ArtistDraw();
12 STDMETHODIMP CowboyDraw();
13};
This trick fills the vtbls
for IArtist
and ICowboy
with _IArtist::Draw
and _ICowboy::Draw
. These functions, in turn, forward to
the more derived class’s implementation of ArtistDraw
and CowboyDraw
. The forwarding shims remove the name conflict
at the cost of an extra vtable
per shim class, an extra
entry per method per vtable
, and an extra virtual function
invocation per call. If this extra cost bothers you, remove it
using the standard ATL tricks [4] :
Don Box
suggested the final efficiency trick, the use of ATL_NO_VTABLE
.
1template <typename Deriving>
2struct ATL_NO_VTABLE _IArtist : public IArtist {
3 STDMETHODIMP Draw() {
4 return static_cast<Deriving*>(this)->ArtistDraw();
5 }
6};
7
8template <typename Deriving>
9struct ATL_NO_VTABLE _ICowboy : public ICowboy {
10 STDMETHODIMP Draw() {
11 return static_cast<Deriving*>(this)->CowboyDraw();
12 }
13};
14
15class ATL_NO_VTABLE CAcePowell :
16 public CComObjectRootEx<CComSingleThreadModel>,
17 public _ICowboy<CAcePowell>,
18 public _IArtist<CAcePowell>
19{
20public:
21BEGIN_COM_MAP(CAcePowell)
22 COM_INTERFACE_ENTRY(ICowboy)
23 COM_INTERFACE_ENTRY(IArtist)
24END_COM_MAP()
25...
26 HRESULT ArtistDraw();
27 HRESULT CowboyDraw();
28};
Don’t Go Off Half-Cocked…
You might think it would be enough to change one of the names by using only one forwarding shim:
1template <typename Deriving>
2struct ATL_NO_VTABLE _ICowboy : public ICowboy {
3 STDMETHODIMP Draw() {
4 return static_cast<Deriving*>(this)->CowboyDraw();
5 }
6};
7
8class ATL_NO_VTABLE CAcePowell :
9 public CComObjectRootEx<CComSingleThreadModel>,
10 public _ICowboy<CAcePowell>,
11 public IArtist {
12public:
13BEGIN_COM_MAP(CAcePowell)
14 COM_INTERFACE_ENTRY(ICowboy)
15 COM_INTERFACE_ENTRY(IArtist)
16END_COM_MAP()
17...
18 HRESULT Draw(); // Use for both IArtist::Draw and
19 // ICowboy::Draw
20 HRESULT CowboyDraw(); // Never called!
21};
Don’t be tempted to try this. Remember that forwarding shims depend on overriding the behavior for the same member function name in the base classes. If you provide an implementation of the function in question with the same name as the function you’re implementing in the forwarding shim in the base, the forwarding shim function will never be called. By implementing one of the functions in the deriving class, you’ve effectively provided an implementation of both, putting you right back where you were in the first place.
Interface Coloring
In the same “sneaky C++ trick” way that
forwarding shims let you fill the appropriate vtbl
entries
even if the compiler won’t cooperate, ATL supports another
technique called interface
coloring. Interface coloring is based on the idea that two
classes can be layout compatible but not type compatible. Two
classes are layout compatible if they have the same vtbl
structure: The functions must be in exactly the same order, and the
parameters must be exactly the same. The names, however, may be
different. For example, the following two classes are layout
compatible because they each result in a vtbl
with the
same number of methods, and every method at the same offset has the
same signature:
1struct ISphere : IUnknown {
2 STDMETHOD(Rotate)(long nDegrees, long* pnOrientation) =0;
3 STDMETHOD(Twirl)(long nVelocity) =0;
4};
5
6struct IRedSphere {
7 // Colored IUnknown methods
8 STDMETHOD(RedQueryInterface)( REFIID riid, void** ppv) =0;
9 STDMETHOD_(ULONG, RedAddRef)() =0;
10 STDMETHOD_(ULONG, RedRelease)() =0;
11
12 // Uncolored ISphere methods
13 STDMETHOD(Rotate)(long nDegrees, long* pnOrientation) =0;
14 STDMETHOD(Twirl)(long nVelocity) =0;
15};
However, because IRedSphere
does not
derive from ISphere
, IRedSphere
is not type
compatible: The compiler won’t let you pass IRedSphere
where ISphere
is expected (without coercion). Cloning the
layout of an interface is known as interface coloring. The layout-compatible
interface is said to be colored because it is identical to the original, except for the names; that
feature is not important to the runtime behavior of your object,
just as a color is unimportant to the runtime behavior of your car.
The names are used at compile time, though, and enable you to
implement multiple versions of the same interface:
1class CDesktopGlobe :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public IRedSphere,
4 public IGlobe,
5 public IPlanet {
6public:
7 ...
8BEGIN_COM_MAP(CDesktopGlobe)
9 // Expose IRedShere when ISphere is requested
10 COM_INTERFACE_ENTRY_IID(IID_ISphere, IRedSphere)
11 COM_INTERFACE_ENTRY(IGlobe)
12 COM_INTERFACE_ENTRY(IPlanet)
13END_COM_MAP()
14 ...
15 // Colored method implementations
16 STDMETHODIMP RedQueryInterface(REFIID riid, void** ppv)
17 { return GetUnknown()->QueryInterface(riid, ppv); }
18
19 STDMETHODIMP_(ULONG) RedAddRef() {
20 _ThreadModel::Increment(&m_cRefSphere);
21 return GetUnknown()->AddRef();
22 }
23
24 STDMETHODIMP_(ULONG) RedRelease() {
25 _ThreadModel::Decrement(&m_cRefSphere);
26 return GetUnknown()->Release();
27 }
28
29private:
30 long m_cRefSphere;
31};
By
deriving from IRedSphere
, we can provide an implementation
of all the colored methods separately from the uncolored ones. By
coloring the IUnknown
methods of IRedSphere
, we
can handle IUnknown
calls on ISphere
separately
from the other implementations of IUnknown
by the other
interfaces. In this case, we’re using RedAddRef
and RedRelease
to keep track of an ISphere
-specific
reference count. And even though we expose IRedSphere
to
the client when it asks for ISphere
, as far as the client
is concerned, it has just an ISphere
interface pointer.
Because IRedSphere
and ISphere
are layout
compatible, as far as COM is concerned, the client is right.
1void TryRotate(IUnknown* punk) {
2 ISphere* ps = 0;
3
4 // Implicit AddRef really a call to RedAddRef
5 if(SUCCEEDED(punk->QueryInterface(IID_ISphere, (void**)&ps))) {
6 // ps actually points to an IRedSphere*
7
8 ps->Rotate();
9 ps->Release(); // Really a call to RedRelease
10 }
11}
COM_INTERFACE_ENTRY_IMPL and COM_INTERFACE_ENTRY_IMPL_IID
Interface coloring is somewhat interesting in
the same way that a car wreck on the side of the road is
interesting: It can be disconcerting as well and can slow traffic.
Beginning with ATL 3.0, the vast majority of IXxxImpl
classes no longer use interface coloring. In ATL 2. x, interface coloring was used for some, but
not all, of the IXxxImpl
classes to perform
interface-specific reference counting. These implementation classes
took the following form:
1template <typename Deriving> class IXxxImpl {...};
Instead of deriving from
the interface the class implemented, the implementation class used
interface coloring to make itself layout compatible with the
implemented interface. This enabled each class to implement its own
reference counting but prohibited the use of the simple COM_INTERFACE_ENTRY
macro. Additional macros were provided
to make the necessary entries in the interface map:
1#define COM_INTERFACE_ENTRY_IMPL(x) \
2 COM_INTERFACE_ENTRY_IID(_ATL_IIDOF(x), x##Impl<_ComMapClass>)
3
4#define COM_INTERFACE_ENTRY_IMPL_IID(iid, x) \
5 COM_INTERFACE_ENTRY_IID(iid, x##Impl<_ComMapClass>)
The interface coloring technique was useful only
if you wanted to track some of the ATL-implemented interfaces.
Beginning with ATL 3.0, ATL uses a more generic
mechanism [5] that tracks reference counts on all
interfaces. Toward that end, all the ATL-implementation classes
actually derive from the interface in question, making the use of COM_INTERFACE_ENTRY_IMPL
and COM_INTERFACE_ENTRY_IMPL_IID
macros unnecessary. All new
and ported code should use COM_INTERFACE_ENTRY
or COM_INTERFACE_ENTRY_IID
instead. Old code that used the IMPL
forms of the macros still compiles under the new ATL
and acts appropriately.
The _ALT_DEBUG_INTERFACES
macro provides this service and is
discussed in Chapter4, “Objects in ATL.”
Tear-Off Interfaces
Although multiple inheritance is preferred when
implementing multiple interfaces, it’s not perfect. One of the
problems is something called vptr-bloat. For each interface a class derives
from, that’s another vptr
per instance of that class.
Beefing up our beach ball implementation can lead to some
significant overhead:
1class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything,
6 public ILethalObject,
7 public ITakeUpSpace,
8 public IWishIWereMoreUseful,
9 public ITryToBeHelpful,
10 public IAmDepressed {...};
Because each beach ball implements eight interfaces, each instance has 32 bytes of overhead on Win32 systems before the reference count or any useful state. If clients actually made heavy use of these interfaces, that wouldn’t be too high of a price to pay. However, my guess is that most clients will use beach balls for their rollable and play-thing abilities. Because the other interfaces will be used infrequently, we’d rather not pay the overhead until they are used. For this, Crispin Goswell invented the tear-off interface, which he described in the article “The COM Programmer’s Cookbook.” [6]
As of this writing, Crispin’s article is available online at http://msdn.com/library/en-us/dncomg/html/msdn_com_co.asp (http://tinysells.com/50).
Standard Tear-Offs
A tear-off interface is an interface that’d you
want to expose on demand but not actually inherit from in the main
class. Instead, an auxiliary class inherits from the interface to
be torn off, and instances of that class are created any time a
client queries for that interface. For example, assuming that few
clients will think to turn a beach ball into a lethal weapon, ILethalObject
would make an excellent tear-off interface
for the CBeachBall
class. Instead of using CComObjectRootEx
as the base class, ATL classes
implementing tear-off interfaces use the CComTearOffObjectBase
as their base class:
1template <class Owner, class ThreadModel = CComObjectThreadModel>
2class CComTearOffObjectBase
3 : public CComObjectRootEx<ThreadModel> {
4public:
5 typedef Owner _OwnerClass;
6 Owner* m_pOwner;
7 CComTearOffObjectBase() { m_pOwner = NULL; }
8};
CComTearOffObjectBase
provides one
additional service, which is the caching of the owner of the tear-off interface. Each tear-off
belongs to an owner object that has torn it off to satisfy a
client’s request. The owner is useful so that the tear-off instance
can access member data or member functions of the owner class:
1class CBeachBallLethalness :
2 public CComTearOffObjectBase<CBeachBall,
3 CComSingleThreadModel>,
4 public ILethalObject {
5public:
6BEGIN_COM_MAP(CBeachBallLethalness)
7 COM_INTERFACE_ENTRY(ILethalObject)
8END_COM_MAP()
9
10 // ILethalObject methods
11 STDMETHODIMP Kill() {
12 m_pOwner->m_gasFill = GAS_HYDROGEN;
13 m_pOwner->HoldNearOpenFlame();
14 return S_OK;
15 }
16};
COM_INTERFACE_ENTRY_TEAR_OFF
To use this tear-off implementation, the owner
class uses the COM_INTERFACE_ENTRY_TEAR_OFF
macro:
1#define COM_INTERFACE_ENTRY_TEAR_OFF(iid, x) \
2 { &iid, \
3 (DWORD_PTR)&ATL::_CComCreatorData< \
4 ATL::CComInternalCreator< ATL::CComTearOffObject< x > > \
5 >::data, \
6 _Creator },
_CComCreatorData
is just a sneaky trick
to fill in the dw
entry of the interface entry with a
function pointer to the appropriate creator function. The creator
function is provided by CComInternalCreator
, which is
identical to CComCreator
except that it calls _InternalQueryInterface
to get the initial interface
instead of QueryInterface
. This is necessary because, as I
show you soon, QueryInterface
on a tear-off instance
forwards to the owner, but we want the initial interface on a new
tear-off to come from the tear-off itself. That is, after all, why
we’re creating the tear-off: to expose that interface.
The pFunc
entry COM_INTERFACE_ENTRY_TEAR_OFF
makes is the first instance
of a nonsimple entry so far in this chapter and, thus, the first
macro we’ve seen that cannot be used as the first entry in the
interface map. The _Creator
function is a static member of
the CComObjectRootBase
class that simply calls the Creator
function pointer held in the dw
parameter:
1static HRESULT WINAPI
2CComObjectRootBase::_Creator(void* pv, REFIID iid,
3 void** ppv, DWORD_PTR dw) {
4 _ATL_CREATORDATA* pcd = (_ATL_CREATORDATA*)dw;
5 return pcd->pFunc(pv, iid, ppv);
6}
The most derived class
of a tear-off implementation is not CComObject
, but rather CComTearOffObject
. CComTearOffObject
knows about
the m_pOwner
member of the base and fills it during
construction. Because each tear-off instance is a separate C++
object, each maintains its own lifetime. However, to live up to the
laws of COM identity, each tear-off forwards requests for new
interfaces to the owner:
1template <class Base>
2class CComTearOffObject : public Base {
3public:
4 CComTearOffObject(void* pv) {
5 ATLASSERT(m_pOwner == NULL);
6 m_pOwner = reinterpret_cast<Base::_OwnerClass*>(pv);
7 m_pOwner->AddRef();
8 }
9
10 ~CComTearOffObject() {
11 m_dwRef = -(LONG_MAX/2);
12 FinalRelease();
13#ifdef _ATL_DEBUG_INTERFACES
14 _AtlDebugInterfacesModule.DeleteNonAddRefThunk(
15 _GetRawUnknown());
16#endif
17 m_pOwner->Release();
18 }
19
20 STDMETHOD_(ULONG, AddRef)() {
21 return InternalAddRef();
22 }
23
24 STDMETHOD_(ULONG, Release)() {
25 ULONG l = InternalRelease();
26 if (l == 0)
27 delete this;
28 return l;
29 }
30
31 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) {
32 return m_pOwner->QueryInterface(iid, ppvObject);
33 }
34};
To use a tear-off, the owner class adds an entry to its interface map:
1class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything,
6 //public ILethalObject, // Implemented by the tear-off
7 public ITakeUpSpace,
8 public IWishIWereMoreUseful,
9 public ITryToBeHelpful,
10 public IAmDepressed {
11public:
12BEGIN_COM_MAP(CBeachBall)
13 COM_INTERFACE_ENTRY(ISphere)
14 COM_INTERFACE_ENTRY(IRollableObject)
15 COM_INTERFACE_ENTRY(IPlaything)
16 COM_INTERFACE_ENTRY_TEAR_OFF(IID_ILethalObject,
17 CBeachBallLethalness)
18 COM_INTERFACE_ENTRY(ITakeUpSpace)
19 COM_INTERFACE_ENTRY(IWishIWereMoreUseful)
20 COM_INTERFACE_ENTRY(ITryToBeHelpful)
21 COM_INTERFACE_ENTRY(IAmDepressed)
22END_COM_MAP()
23...
24private:
25 GAS_TYPE m_gasFill;
26 void HoldNearOpenFlame();
27 // Tear-offs are generally friends
28 friend class CBeachBallLethalness;
29};
Because the owner class is no longer deriving from ILethalObject
, each instance is 4 bytes lighter. However,
when the client queries for ILethalObject
, we’re spending
4 bytes for the ILethalObject vptr
in CBeachBallLethalness
, 4 bytes for the CBeachBallLethalness
reference count, and 4 bytes for the m_pOwner
back pointer. You might wonder how spending 12
bytes to save 4 bytes actually results in a savings. I’ll tell you:
volume! Or rather, the lack thereof. Because we’re paying only the
12 bytes during the lifetime of the tear-off instance and we’ve
used extensive profiling to determine ILethalObject
is
rarely used, the overall object footprint should be smaller.
Tear-Off Caveats
Before wrapping yourself in the perceived efficiency of tear-offs, you should be aware of these cautions:
Tear-offs are only for rarely used interfaces. Tear-off interfaces are an implementation trick to be used to reduce
vptr
bloat when extensive prototyping has revealed this to be a problem. If you don’t have this problem, save yourself the trouble and avoid tear-offs.Tear-offs are for intra-apartment use only. The stub caches a tear-off interface for the life of an object. In fact, the current implementation of the stub manager caches each interface twice, sending the overhead of that particular interface from 12 bytes to 24 bytes.
Tear-offs should contain no state of their own. If a tear-off contains its own state, there will be one copy of that state per tear-off instance, breaking the spirit, if not the laws, of COM identity. If you have per-interface state, especially large state that you want to be released when no client is using the interface, use a cached tear-off.
Cached Tear-Offs
You might have noticed that every query for ILethalObject
results in a new tear-off instance, even if
the client already holds an ILethalObject
interface
pointer. This might be fine for a single interface tear-off, but
what about a related group of interfaces that will be used
together? [7] For example, imagine moving the other
rarely used interfaces of CBeachBall
to a single tear-off
implementation:
The Control
interfaces fit into this category for objects that
also support nonvisual use.
1class CBeachBallAttitude :
2 public CComTearOffObjectBase<CBeachBall,
3 CComSingleThreadModel>,
4 public ITakeUpSpace,
5 public IWishIWereMoreUseful,
6 public ITryToBeHelpful,
7 public IAmDepressed {
8public:
9BEGIN_COM_MAP(CBeachBallAttitude)
10 COM_INTERFACE_ENTRY(ITakeUpSpace)
11 COM_INTERFACE_ENTRY(IWishIWereMoreUseful)
12 COM_INTERFACE_ENTRY(ITryToBeHelpful)
13 COM_INTERFACE_ENTRY(IAmDepressed)
14END_COM_MAP()
15...
16};
The following use of this tear-off implementation compiles and exhibits the appropriate behavior, but the overhead of even a single tear-off is exorbitant:
1class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything
6 // No tearoff interfaces in base class list
7{
8public:
9BEGIN_COM_MAP(CBeachBall)
10 COM_INTERFACE_ENTRY(ISphere)
11 COM_INTERFACE_ENTRY(IRollableObject)
12 COM_INTERFACE_ENTRY(IPlaything)
13 // tearoffs are listed in the interface map
14 COM_INTERFACE_ENTRY_TEAR_OFF(IID_ILethalObject,
15 CBeachBallLethalness)
16 COM_INTERFACE_ENTRY_TEAR_OFF(IID_ITakeUpSpace,
17 CBeachBallAttitude)
18 COM_INTERFACE_ENTRY_TEAR_OFF(IID_IWishIWereMoreUseful,
19 CBeachBallAttitude)
20 COM_INTERFACE_ENTRY_TEAR_OFF(IID_ITryToBeHelpful,
21 CBeachBallAttitude)
22 COM_INTERFACE_ENTRY_TEAR_OFF(IID_IAmDepressed,
23 CBeachBallAttitude)
24END_COM_MAP()
25...
26};
Because we’ve grouped the “attitude” interfaces together into a single tear-off implementation, every time the client queries for any of them, it pays the overhead of all of them. To allow this kind of grouping but avoid the overhead of creating a new instance for every query, ATL provides an implementation of a cached tear-off. The owner holds a cached tear-off if there is even one outstanding interface to the tear-off. The initial query creates and caches the tear-off. Subsequent queries use the cached tear-off. The final release deletes the tear-off instance.
COM_INTERFACE_ENTRY_CACHED_TEAR_OFF
To support caching tear-offs, ATL provides another interface macro:
1#define COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk) \
2 { &iid, \
3 (DWORD_PTR)&ATL::_CComCacheData< \
4 ATL::CComCreator< ATL::CComCachedTearOffObject< x > >, \
5 (DWORD_PTR)offsetof(_ComMapClass, punk) >::data, \
6 _Cache },
The _CComCacheData
class is used to
stuff a pointer into an _ATL_CACHEDATA
structure:
1struct _ATL_CACHEDATA {
2 DWORD dwOffsetVar;
3 _ATL_CREATORFUNC* pFunc;
4};
The use of this structure allows the dw
to point to a Creator function pointer as well as another member,
an offset. The offset is from the base of the owner class to the
member data that is used to cache the pointer to the tear-off. The _Cache
function, another static member function of CComObjectRootBase
, uses the offset to calculate the
address of the pointer and checks the pointer to determine whether
to create a new instance of the cached tear-off:
1static HRESULT WINAPI
2CComObjectRootBase::_Cache(
3 void* pv,
4 REFIID iid,
5 void** ppvObject,
6 DWORD_PTR dw)
7{
8 HRESULT hRes = E_NOINTERFACE;
9 _ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw;
10 IUnknown** pp = (IUnknown**)((DWORD_PTR)pv + pcd->dwOffsetVar);
11 if (*pp == NULL)
12 hRes = pcd->pFunc(pv, __uuidof(IUnknown), (void**)pp);
13 if (*pp != NULL)
14 hRes = (*pp)->QueryInterface(iid, ppvObject);
15 return hRes;
16}
Just as an instance of a tear-off uses CComTearOffObject
instead of CComObject
to
provide the implementation of IUnknown
, cached tear-offs
use CComCachedTearOffObject
.
CComCachedTearOffObject
is nearly identical to CComAggObject
[8] because of the way that the lifetime
and identity of the tear-off are subsumed by that of the owner. The
only difference is that the cached tear-off, like the tear-off,
initializes the m_pOwner
member.
Discussed in Chapter4, “Objects in ATL.”
Replacing the inefficient use of COM_INTERFACE_ENTRY_TEAR_OFF
with COM_INTERFACE_ENTRY_CACHED_TEAR_OFF
looks like this:
1class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything {
6public:
7BEGIN_COM_MAP(CBeachBall)
8 COM_INTERFACE_ENTRY(ISphere)
9 COM_INTERFACE_ENTRY(IRollableObject)
10 COM_INTERFACE_ENTRY(IPlaything)
11 COM_INTERFACE_ENTRY_TEAR_OFF(IID_ILethalObject,
12 CBeachBallLethalness)
13 COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITakeUpSpace,
14 CBeachBallAttitude, m_spunkAttitude.p)
15 COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_IWishIWereMoreUseful,
16 CBeachBallAttitude, m_spunkAttitude.p)
17 COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITryToBeHelpful,
18 CBeachBallAttitude, m_spunkAttitude.p)
19 COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_IAmDepressed,
20 CBeachBallAttitude, m_spunkAttitude.p)
21END_COM_MAP()
22DECLARE_GET_CONTROLLING_UNKNOWN() // See the Aggregation section
23...
24public:
25 CComPtr<IUnknown> m_spunkAttitude;
26};
Another Use for Cached Tear-Offs
Cached tear-offs have another use that is in
direct opposition to standard tear-offs: caching per-interface
resources. For example, imagine a dictionary object that implements
a rarely used IHyphenation
interface:
1interface IHyphenation : public IUnknown {
2 HRESULT Hyphenate([in] BSTR bstrUnhyphed,
3 [out, retval] BSTR* pbstrHyphed);
4};
Performing hyphenation is a matter of consulting
a giant look-up table. If a CDictionary
object were to
implement the IHyphenation
interface, it would likely doso as a cached tear-off to manage the
resources associated with the look-up table. When the hyphenation
cached tear-off is first created, it acquires the look-up table.
Because the tear-off is cached, subsequent queries use the same
look-up table. After all references to the IHyphenation
interface are released, the look-up table can be released. If we
had used a standard tear-off for this same functionality, a naïve
implementation would have acquired the resources for the look-up
table for each tear-off.
Aggregation: The Controlling Outer
As with tear-offs, aggregation enables you to
separate the code for a single identity into multiple objects.
However, whereas using tear-offs require shared source between the
owner and the tear-off class, aggregation does not. The controlling
outer and the controlling inner do not have to share the same
server or even the same implementation language (although they do
have to share the same apartment). If you like, you can consider an
aggregated object a kind of “binary cached tear-off.” As with a
cached tear-off, an aggregated instance’s lifetime and identity are
subsumed by that of the controlling outer. Just like a cached
tear-off, an aggregated instance must have a way to obtain the
interface pointer of the controlling outer. In a tear-off, we pass
the owner as a constructor argument. In aggregation, we do the same
thing, but using the COM constructor that accepts a single,
optional constructor argument – that is, the pUnkOuter
parameter of IClassFactory::CreateInstance
and its
wrapper, CoCreateInstance
:
1interface IClassFactory : IUnknown {
2HRESULT CreateInstance([in, unique]IUnknown* pUnkOuter,
3[in] REFIID riid,
4[out, iid_is(riid)] void **ppvObject);
5HRESULT LockServer([in] BOOL fLock);
6};
7
8WINOLEAPI CoCreateInstance([in] REFCLSID rclsid,
9[in, unique]LPUNKNOWN pUnkOuter,
10[in] DWORD dwClsContext,
11[in] REFIID riid,
12[out, iid_is(riid)] LPVOID FAR* ppv);
In Chapter 4, “Objects in ATL,” I discussed how
ATL supports aggregation as a controlled inner using CComAggObject
(or CComPolyObject
). In this
chapter, I show you the four macros that ATL provides for the
controlling outer in the aggregation relationship.
Planned Versus Blind Aggregation
After an aggregate is created, the controlling outer has two choices of how to exposed the interface(s) of the aggregate as its own. The first choice is planned aggregation. In planned aggregation, the controlling outer wants the inner to expose one of a set of interfaces known by the outer. Figure 6.2 illustrates planned aggregation.
Figure 6.2. Planned aggregation

The downside to this technique is that if the
inner’s functionality grows, clients using the outer cannot gain
access to the additional functionality. The upside is that this
could be exactly what the outer had in mind. For example, consider
the standard interface IPersist
:
1interface IPersist : IUnknown {
2HRESULT GetClassID([out] CLSID *pClassID);
3}
If the outer were to blindly expose the inner’s
implementation of IPersist
, when the client called GetClassID
, it would get the CLSID of the inner, not the
outer. Because the client wants the outer object’s class
identifier, we have again broken the spirit of the COM identity
laws. Planned aggregation helps prevent this breach.
Blind aggregation, on the other hand, allows the outer’s functionality to grow with the inner’s, but it provides the potential for exposing identity information from the inner. For this reason, blind aggregation should be avoided. Figure 6.3 shows blind aggregation.
Figure 6.3. Blind aggregation

Manual Versus Automatic Creation
COM_INTERFACE_ENTRY_AGGREGATE and COM_INTERFACE_ENTRY_AGGREGATE_BLIND
ATL provides support for both planned and blind aggregation via the following two macros:
1#define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk) \
2{ &iid, (DWORD_PTR)offsetof(_ComMapClass, punk), _Delegate },
3
4#define COM_INTERFACE_ENTRY_AGGREGATE_BLIND(punk) \
5{ NULL, (DWORD_PTR)offsetof(_ComMapClass, punk), _Delegate},
These macros assume that the aggregate has
already been created manually and that the interface pointer is
stored in the punk
parameter to the macro. The _Delegate
function forwards the QueryInterface
request to that pointer:
1static HRESULT WINAPI
2CComObjectRootBase::
3
4_Delegate(
5void* pv,
6REFIID iid,
7void** ppvObject,
8DWORD dw)
9{
10HRESULT hRes = E_NOINTERFACE;
11IUnknown* p = *(IUnknown**)((DWORD_PTR)pv + dw);
12if (p != NULL) hRes = p->QueryInterface(iid, ppvObject);
13return hRes;
14}
To use aggregation of an inner that has been
manually created, the classes use COM_INTERFACE_ENTRY_AGGREGATE
or COM_INTERFACE_ENTRY_AGGREGATE_BLIND
in the interface
map:
1class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything {
6public:
7BEGIN_COM_MAP(CBeachBall)
8 COM_INTERFACE_ENTRY(ISphere)
9 COM_INTERFACE_ENTRY(IRollableObject)
10 COM_INTERFACE_ENTRY(IPlaything)
11COM_INTERFACE_ENTRY_AGGREGATE(IID_ILethalObject,
12m_spunkLethalness)
13COM_INTERFACE_ENTRY_AGGREGATE_BLIND(m_spunkAttitude)
14END_COM_MAP()
15DECLARE_GET_CONTROLLING_UNKNOWN()
16DECLARE_PROTECT_FINAL_CONSTRUCT()
17HRESULT FinalConstruct() {
18HESULT hr;
19hr = CoCreateInstance(CLSID_Lethalness,
20GetControllingUnknown(),
21CLSCTX_INPROC_SERVER,
22IID_IUnknown,
23(void**)&m_spunkLethalness);
24if( SUCCEEDED(hr) ) {
25hr = CoCreateInstance(CLSID_Attitude,
26GetControllingUnknown(),
27CLSCTX_INPROC_SERVER,
28IID_IUnknown,
29(void**)&m_spunkAttitude);
30}
31return hr;
32}
33void FinalRelease() {
34m_spunkLethalness.Release();
35m_spunkAttitude.Release();
36}
37...
38public:
39CComPtr<IUnknown> m_spunkLethalness;
40CComPtr<IUnknown> m_spunkAttitude;
41};
Notice that I have used
the FinalConstruct
method to create the aggregates so that
failure stops the creation process. Notice also that because I’ve
got a FinalConstruct
that hands out interface pointers to
the object being created, I’m using DECLARE_PROTECT_FINAL_CONSTRUCT
to protect against
premature destruction. I’ve also got a FinalRelease
method
to manually release the aggregate interface pointers to protect
against double destruction. Aggregation was one of the chief
motivations behind the multiphase construction of ATL-based COM
objects, so it’s not surprising to see all the pieces used in this
example.
However, one thing I’ve not yet mentioned is the DECLARE_GET_CONTROLLING_UNKNOWN
macro. The controlling
unknown is a pointer to the most controlling outer. Because
aggregation can go arbitrarily deep, when aggregating, the outer
needs to pass the pUnkOuter
of the outermost object. To
support this, ATL provides the GET_CONTROLLING_UNKNOWN
macro to give an object the definition of a GetControllingUnknown
function:
1#define DECLARE_GET_CONTROLLING_UNKNOWN() public: \
2virtual IUnknown* GetControllingUnknown() { \
3return GetUnknown(); }
You might question the value of this function
because it simply forwards to GetUnknown
, but notice that
it’s virtual. If the object is actually being created as an
aggregate while it is aggregating, GetControllingUnknown
is overridden in CComContainedObject
:
1template <class Base> class CComContainedObject : public Base {
2...
3IUnknown* GetControllingUnknown()
4{ return m_pOuterUnknown; }
5...
6};
So, if the object is standalone, GetControllingUnknown
returns the IUnknown*
of
the object, but if the object is itself being aggregated, GetControllingUnknown
returns the IUnknown*
of
the outermost outer.
COM_INTERFACE_ENTRY_AUTOAGGREGATE and COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND
If you’re not doing any initialization of the aggregates, sometimes it seems a waste to create them until (or unless) they’re needed. For automatic creation of aggregates on demand, ATL provides the following two macros:
1#define COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid) \
2{ &iid, \
3(DWORD_PTR)&ATL::_CComCacheData< \
4ATL::CComAggregateCreator<_ComMapClass, &clsid>, \
5(DWORD_PTR)offsetof(_ComMapClass, punk)>::data, \
6_Cache },
7
8#define COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk, clsid) \
9{ NULL, \
10(DWORD_PTR)&ATL::_CComCacheData< \
11ATL::CComAggregateCreator<_ComMapClass, &clsid>, \
12(DWORD_PTR)offsetof(_ComMapClass, punk)>::data, \
13_Cache },
The only new thing in these macros is the CComAggregateCreator
, which simply performs the CoCreateInstance
the first time the interface is
requested:
1template <class T, const CLSID* pclsid>
2class CComAggregateCreator {
3public:
4static HRESULT WINAPI CreateInstance(void* pv, REFIID,
5LPVOID* ppv) {
6T* p = (T*)pv;
7return CoCreateInstance(*pclsid, p->GetControllingUnknown(),
8CLSCTX_INPROC, __uuidof(IUnknown), ppv);
9}
10};
Using automatic creation simplifies the outer’s code somewhat:
1class CBeachBall :
2 public CComObjectRootEx<CBeachBall>,
3 public ISphere,
4 public IRollableObject,
5 public IPlaything {
6public:
7BEGIN_COM_MAP(CBeachBall)
8 COM_INTERFACE_ENTRY(ISphere)
9 COM_INTERFACE_ENTRY(IRollableObject)
10 COM_INTERFACE_ENTRY(IPlaything)
11COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_ILethalObject,
12m_spunkLethalness, CLSID_Lethalness)
13COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(m_spunkAttitude,
14CLSID_Attitude)
15END_COM_MAP()
16
17DECLARE_GET_CONTROLLING_UNKNOWN()
18
19 void FinalRelease() {
20 m_spunkLethalness.Release();
21 m_spunkAttitude.Release();
22 }
23...
24public:
25 CComPtr<IUnknown> m_spunkLethalness;
26 CComPtr<IUnknown> m_spunkAttitude;
27};
Although we no longer need to perform the
creation in FinalConstruct
, we’re still required to use DECLARE_GET_CONTROLLING_UNKNOWN
and to provide storage for
the aggregated interfaces. We still release the interfaces manually
in FinalRelease
, as well, to avoid double destruction.
Aggregating the Free Threaded Marshaler
The ATL Object Wizard directly supports one
particularly interesting use of aggregation: aggregating the
implementation of IMarshal
provided by the Free Threaded
Marshaler (FTM). Any object that aggregates the FTM is said to be
an apartment-neutral object.
Normally, passing an interface pointer between apartments, even in
the same process, results in a proxy-stub pair. The proxy-stub pair
maintains the concurrency and synchronization requirements of both
the object and the client, but it also adds overhead. In-process
objects that provide their ownsynchronization and prefer to snuggle up to the
client without the overhead of the proxy-stub can aggregate the
FTM. By aggregating the FTM, an object short-circuits the creation
of the proxy-stub for in-process objects. Therefore, the object can
be passed between apartments in the same address space without the
overhead of a proxy-stub.
The wizard generates the following code when the Free Threaded Marshaler option is checked in the ATL Object Wizard:
1class ATL_NO_VTABLE CBowlingBall :
2public CComObjectRootEx<CComMultiThreadModel>,
3public CComCoClass<CBowlingBall, &CLSID_BowlingBall>,
4public IBowlingBall
5{
6public:
7CBowlingBall() { m_pUnkMarshaler = NULL; }
8
9DECLARE_REGISTRY_RESOURCEID(IDR_BOWLINGBALL)
10DECLARE_GET_CONTROLLING_UNKNOWN()
11DECLARE_PROTECT_FINAL_CONSTRUCT()
12
13BEGIN_COM_MAP(CBowlingBall)
14COM_INTERFACE_ENTRY(IBowlingBall)
15COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
16END_COM_MAP()
17
18HRESULT FinalConstruct() {
19return CoCreateFreeThreadedMarshaler(GetControllingUnknown(),
20&m_pUnkMarshaler.p);
21}
22void FinalRelease() {
23m_pUnkMarshaler.Release();
24}
25
26CComPtr<IUnknown> m_pUnkMarshaler;
27...
28};
Because the CLSID of the FTM is not available,
instead of using auto-creation, ATL uses CoCreateFreeThreadMarshaler
to create an instance of the
FTM in the FinalConstruct
method.
FTM Danger, Will Robinson! Danger! Danger!
Aggregating the FTM is easy, so I should mention a couple of big responsibilities that you, the developer, accept by aggregating the FTM:
Apartment-neutral objects must be thread safeYou can mark your class
ThreadingModel=Apartment
all day long, but because your object can be passed freely between apartments in the same process and, therefore, can used simultaneously by multiple threads, you’d better useCComMultiThreadModel
and at least object-level locking. Fortunately, the ATL Object Wizard makes the FTM option available for selection only if you choose a threading model of Both or Neutral.Apartment-neutral objects are not aggregatable. Aggregating the FTM depends on being able to implement
IMarshal
. If the outer decides to implementIMarshal
and doesn’t ask the inner object, the inner can no longer be apartment neutral.Apartment-neutral objects cannot cache interface pointers. An apartment-neutral object is said to be apartment neutral because it doesn’t care which apartment it is accessed from. However, other objects that an apartment-neutral object uses might or might not also be apartment-neutral. Interface pointers to objects that aren’t apartment neutral can be used only in the apartment to which they belong. If you’re lucky, the apartment-neutral object attempting to cache and use an interface pointer from another apartment will have a pointer to a proxy. Proxies know when they are being accessed outside their apartments and return
RPC_E_WRONG_THREAD
for all such method calls. If you’re not so lucky, the apartment-neutral object obtains a raw interface pointer. Imagine the poor single-threaded object accessed simultaneously from multiple apartments as part of its duty to the apartment-neutral object. It will most likely work just fine until you need to give a demo to your biggest client.
The only safe way to cache interface pointers in
an apartment-neutral object is as cookies obtained from the Global
Interface Table (GIT). The GIT is a process-global object provided
to map apartment-specific interface pointers to apartment-neutral
cookies and back. The GIT was invented after the FTM and is
provided in the third service pack to NT 4.0, the DCOM patch for
Windows 95, and out of the box with Windows 98/2000/XP. If you’re
aggregating the FTM and caching interface pointers, you must use
the GIT. ATL provides the CComGITPtr
class, discussed in Chapter 3, “ATL Smart
Types,” to make dealing with the GIT easier.
For an in-depth discussion of the FTM, the GIT, and their use, read Essential COM (Addison-Wesley, 1997), by Don Box.
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
1class CBigBeachBall :
2 public CBeachBall,
3 public IBigObject {
4public:
5BEGIN_COM_MAP(CBigBeachBall)
6 COM_INTERFACE_ENTRY(IBigObject)
7 // All entries from CBeachBall base?
8END_COM_MAP()
9...
10};
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:
1#define COM_INTERFACE_ENTRY_CHAIN(classname) \
2{ NULL, (DWORD_PTR)&ATL::_CComChainData<classname, \
3_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:
1static HRESULT WINAPI
2CComObjectRootBase::
3
4_Chain(void* pv, REFIID iid,
5void** ppvObject, DWORD dw) {
6_ATL_CHAINDATA* pcd = (_ATL_CHAINDATA*)dw;
7void* p = (void*)((DWORD_PTR)pv + pcd->dwOffset);
8return InternalQueryInterface(p, pcd->pFunc(), iid, ppvObject);
9}
If the _Chain
function returns a
failure – for example, if the base class doesn’t support the requested
interface – the search continues with the next entry in the table:
1class CBigBadBeachBall :
2 public CBeachBall,
3 public IBigObject,
4 public IBadObject {
5public:
6BEGIN_COM_MAP(CBigBadBeachBall)
7 COM_INTERFACE_ENTRY(IBigObject)
8COM_INTERFACE_ENTRY_CHAIN(CBeachBall)
9COM_INTERFACE_ENTRY(IBadObject)
10END_COM_MAP()
11...
12};
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:
1class CBetterBeachBall :
2 public CBeachBall {
3public:
4BEGIN_COM_MAP(CBetterBeachBall)
5COM_INTERFACE_ENTRY(IUnknown)
6COM_INTERFACE_ENTRY_CHAIN(CBeachBall)
7END_COM_MAP()
8...
9};
Just Say “No”
COM_INTERFACE_ENTRY_NOINTERFACE
Sometimes, you want to short-circuit the
interface request by explicitly returning E_NOINTERFACE
when a specific interface is requested. For this, ATL provides COM_INTERFACE_ENTRY_NOINTERFACE:
1#define COM_INTERFACE_ENTRY_NOINTERFACE(x) \
2{ &_ATL_IIDOF(x), NULL, _NoInterface },
The _NoInterface
function does pretty much
what you’d expect:
1static HRESULT WINAPI
2CComObjectRootBase::
3
4_NoInterface(void*, REFIID, void**,
5DWORD_PTR)
6{ return E_NOINTERFACE; }
This interface map macro is handy when you’ve got blind entries in the interface map, as in blind aggregation or chaining, and you want to remove functionality that the inner object or the base class provides. For example
1class CBigNiceBeachBall :
2 public CBeachBall,
3 public IBigObject {
4public:
5BEGIN_COM_MAP(CBigNiceBeachBall)
6 COM_INTERFACE_ENTRY(IBigObject)
7COM_INTERFACE_ENTRY_NOINTERFACE(ILethalObject)
8COM_INTERFACE_ENTRY_CHAIN(CBeachBall)
9END_COM_MAP()
10...
11};
Debugging
COM_INTERFACE_ENTRY_BREAK
Sometimes you need to debug your QueryInterface
implementation. Maybe you’re building a
custom COM_MAP
macro. Maybe you’re seeing weird behavior
on some interfaces. Sometimes the easiest way to track down a
problem is in the debugger. This is where COM_INTERFACE_ENTRY_BREAK
comes in:
1#define COM_INTERFACE_ENTRY_BREAK(x) \
2{ &_ATL_IIDOF(x), NULL, _Break },
The _Break
function outputs some
helpful debugging information and calls DebugBreak
:
1static HRESULT WINAPI
2CComObjectRootbase::
3
4_Break(void*, REFIID iid, void**, DWORD) {
5(iid);
6_ATLDUMPIID(iid, _T("Break due to QI for interface "), S_OK);
7DebugBreak();
8return S_FALSE;
9}
The call to DebugBreak
is just like a breakpoint set in your debugger.
It gives the active debugger the chance to take control of the
process. When you’re debugging, you can set other breakpoints and
continue executing.
Extensibility
COM_INTERFACE_ENTRY_FUNC and COM_INTERFACE_ENTRY_FUNC_BLIND
ATL provides two macros for putting raw entries into the interface map:
1#define COM_INTERFACE_ENTRY_FUNC(iid, dw, func) \
2{ &iid, dw, func },
3
4#define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func) \
5{ NULL, dw, func },
These macros are the universal back door to
ATL’s implementation of QueryInterface
. If you come up
with another way of exposing COM interfaces, you can use these
macros to achieve them, as long as it lives up to the laws of COM
identity.
Direct Access to the this Pointer
One identity trick you can perform using COM_INTERFACE_ENTRY_FUNC
was kicked around the ATL Mailing
List for quite a while but was ultimately perfected by Don Box. (A
slightly modified version of his solution is provided next.) In Chapter 4, “Objects in
ATL,” I presented the CreateInstance
static member
functions of CComObject
, CComAgg-Object
, and CComPolyObject
when using private initialization. The CreateInstance
method performed the same job as a Creator
would but returned a pointer to the this
pointer of the
object instead of to only one of the interfaces. This was useful
for calling member functions or setting member data that was not
exposed via interfaces. We used this technique because it was
unsafe to perform a cast. However, why not make QueryInterface
perform the cast safely? In other words,
why not add an entry to the interface map that returns the object’s this
pointer? Imagine a global function with the following
implementation:
1inline
2HRESULT WINAPI _This(void* pv, REFIID iid,
3 void** ppvObject, DWORD) {
4 ATLASSERT(iid == IID_NULL);
5 *ppvObject = pv;
6 return S_OK;
7}
This function takes the first parameter, pv
, which points to the object’s this
pointer and
hands it out directly in ppvObject
. Notice also that this
function does not AddRef
the resultant interface pointer.
Because it’s returning an object pointer, not an interface pointer,
it’s not subject to the laws of COM. Remember, the this
pointer is useful only within the server. To make sure that any
out-of-apartment calls to QueryInterface
fail, be sure to
pick an interface ID without a proxy-stub, such as IID_NULL
.
For example, imagine implementations of the following interfaces, creating a simple object model:
1interface IBalloonMan : IUnknown {
2 HRESULT CreateBalloon(long rgbColor, IBalloon** ppBalloon);
3 HRESULT SwitchColor(IBalloon* pBalloon, long rgbColor);
4};
5
6interface IBalloon : IUnknown {
7 [propget] HRESULT Color([out, retval] long *pVal);
8};
Notice that the balloon’s color can’t be changed
via IBalloon
, but the implementation of IBalloonMan
can give you a balloon of the color you want.
If the implementations of IBalloonMan
and IBalloon
share the same server, the implementation of IBalloon
can expose its this
pointer via the _This
function like this:
1class ATL_NO_VTABLE CBalloon :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public CComCoClass<CBalloon>,
4 public IBalloon {
5public:
6DECLARE_REGISTRY_RESOURCEID(IDR_BALLOON)
7DECLARE_NOT_AGGREGATABLE(CBalloon)
8
9DECLARE_PROTECT_FINAL_CONSTRUCT()
10
11BEGIN_COM_MAP(CBalloon)
12 COM_INTERFACE_ENTRY(IBalloon)
13COM_INTERFACE_ENTRY_FUNC(IID_NULL, 0, _This)
14END_COM_MAP()
15 // IBalloon
16public:
17 STDMETHOD(get_Color)(/*[out, retval]*/ long *pVal);
18
19private:
20 COLORREF m_rgbColor;
21 friend class CBalloonMan;
22};
Because CBalloonMan
is a friend of CBalloon
, CBalloonMan
can set the private color
data member of CBalloon
, assuming that it can obtain the
object’s this
pointer. CBalloon
’s special entry
for IID_NULL
lets CBalloonMan
do just that:
1STDMETHODIMP CBalloonMan::CreateBalloon(long rgbColor,
2 IBalloon** ppBalloon) {
3 // Create balloon
4 *ppBalloon = 0;
5 HRESULT hr = CBalloon::CreateInstance(0, ppBalloon);
6
7 if( SUCCEEDED(hr) ) {
8// Use backdoor to acquire CBalloon's this pointer
9CBalloon* pBalloonThis = 0;
10hr = (*ppBalloon)->QueryInterface(IID_NULL,
11(void**)&pBalloonThis);
12if( SUCCEEDED(hr) ) {
13 // Use CBalloon's this pointer for private initialization
14 pBalloonThis->m_rgbColor = rgbColor;
15 }
16 }
17 return hr;
18}
The benefit to this technique over the private
initialization technique that the CreateInstance
member
function of CComObject
et al exposes is that the this
pointer can be obtained after creation:
1STDMETHODIMP CBalloonMan::SwitchColor(IBalloon* pBalloon,
2 long rgbColor) {
3// Use backdoor to acquire CBalloon's this pointer
4CBalloon* pBalloonThis = 0;
5HRESULT hr = pBalloon->QueryInterface(IID_NULL, (void**)&pBalloonThis);
6if (SUCCEEDED(hr)) {
7 hr = pBalloonThis->m_rgbColor = rgbColor;
8 }
9 return hr;
10}
Clearly, this technique is a back-door hack with limited usefulness; it should not be used to subvert the binary boundary between client and object. However, it does have its own special charm. For objects that share the same apartment in the same server, it’s a valid way to discover just who is who. If you find yourself using this technique, you might find the following macro to be a useful shortcut:
1#define COM_INTERFACE_ENTRY_THIS() \
2 COM_INTERFACE_ENTRY_FUNC(IID_NULL, 0, _This)
Per-Object Interfaces
Sometimes it’s useful to handle interfaces on a
per-object basis instead of a per-class basis. Another friend of
mine, Martin Gudgin, provided the following example. If you want to
implement something known as a “smart proxy,” you have to keep
track of the list of interfaces each object supports, and you might
not know what those are until runtime. Each smart proxy object has
its own list of interfaces, which can easily be managed by a member
function. Unfortunately, interface maps can’t hold member functions
(believe me, I tried). However, you can use a combination of COM_INTERFACE_ENTRY_FUNC_BLIND
and a static member
function to perform the forwarding to a member function:
1class ATL_NO_VTABLE CSmartProxy :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public CComCoClass<CSmartProxy, &CLSID_SmartProxy>,
4 public IUnknown
5{
6public:
7DECLARE_REGISTRY_RESOURCEID(IDR_SMARTPROXY)
8DECLARE_PROTECT_FINAL_CONSTRUCT()
9
10BEGIN_COM_MAP(CSmartProxy)
11 COM_INTERFACE_ENTRY(IUnknown)
12COM_INTERFACE_ENTRY_FUNC_BLIND(0, _QI)
13END_COM_MAP()
14
15public:
16static HRESULT WINAPI _QI(void* pv, REFIID iid,
17void** ppvObject, DWORD) {
18// Forward to QI member function
19return ((CSmartProxy*)pv)->QI(iid, ppvObject);
20}
21// Per-object implementation of QI
22HRESULT QI(REFIID riid, void** ppv);
23};
Of course, you might wonder why you’d go to all
this trouble to get back to the per-object implementation of QueryInterface
that you’ve come to know and love, but
that’s ATL.
Summary
Even when you understand and commit to the laws of COM identity, you’ll find that they aren’t very restrictive. Prefer multiple inheritance, but do not feel that ATL limits you to that technique. You can extend the interface map to support any identity trick that ATL doesn’t support directly.