One of the problems with MI is that of name collisions. Imagine the following
interfaces:
interface ICowboy : IUnknown {
HRESULT Draw();
};
interface IArtist : IUnknown {
HRESULT Draw();
};
Because both Draw
methods have the same signature, using straight MI requires a single shared
implementation:
// Ace Powell was a cowboy/artist who lived in the
// western US from 1912 to his death in 1978. I’d
// like to thank Tim Ewald for this fabulous example,
// which I have used to death for years.
class CAcePowell :
public CComObjectRootEx<CComSingleThreadModel>,
public ICowboy,
public IArtist {
public:
BEGIN_COM_MAP(CAcePowell)
COM_INTERFACE_ENTRY(ICowboy)
COM_INTERFACE_ENTRY(IArtist)
END_COM_MAP()
…
HRESULT Draw()
{ /* Act as a cowboy or an artist? */ }
};
Since the implied meaning of
Draw is very different for an artist than it is
for a cowboy, we’d like to be able to provide two
Draw implementations. For that, we a technique
long known to the C++ community that I’ll call “forwarding
shims.”
The problem is that C++ has no syntax to be able to distinguish methods with
the same signature from different bases in the derived class. For example, the
following is not legal C++:
class CAcePowell :
public CComObjectRootEx<CComSingleThreadModel>,
public ICowboy,
public IArtist {
public:
BEGIN_COM_MAP(CAcePowell)
COM_INTERFACE_ENTRY(ICowboy)
COM_INTERFACE_ENTRY(IArtist)
END_COM_MAP()
…
HRESULT IArtist::Draw(); // error
HRESULT ICowboy::Draw(); // error
};
However, we can certainly distinguish the methods in individual base classes,
e.g.
struct _IArtist : public IArtist {
STDMETHODIMP Draw() { return ArtistDraw(); }
STDMETHOD(ArtistDraw)() =0;
};
struct _ICowboy : public ICowboy {
STDMETHODIMP Draw() { return CowboyDraw(); }
STDMETHOD(CowboyDraw)() =0;
};
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.
Since both shims derive from the interface in question, they interfaces
IArtist
and ICowboy
can still appear in the interface map without difficulty:
class CAcePowell :
public CComObjectRootEx<CComSingleThreadModel>,
public _ICowboy,
public _IArtist {
public:
BEGIN_COM_MAP(CAcePowell)
COM_INTERFACE_ENTRY(ICowboy)
COM_INTERFACE_ENTRY(IArtist)
END_COM_MAP()
…
HRESULT ArtistDraw();
HRESULT CowboyDraw();
};
This trick fills the vtables for
IArtist and
ICowboy with
_IArtist::Draw and
_ICowboy::Draw.
These functions, in turn, forward to the more derived class’s
implementation of the
ArtistDraw and
CowboyDraw. The forwarding shims remove our 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:
template <typename Deriving>
struct ATL_NO_VTABLE _IArtist : public IArtist {
STDMETHODIMP Draw() {
return
static_cast<Deriving*>(this)->ArtistDraw();
}
};
template <typename Deriving>
struct ATL_NO_VTABLE _ICowboy : public ICowboy {
STDMETHODIMP Draw() {
return
static_cast<Deriving*>(this)->CowboyDraw();
}
};
class ATL_NO_VTABLE CAcePowell :
public CComObjectRootEx<CComSingleThreadModel>,
public _ICowboy<CAcePowell>,
public _IArtist<CAcePowell> {
public:
BEGIN_COM_MAP(CAcePowell)
COM_INTERFACE_ENTRY(ICowboy)
COM_INTERFACE_ENTRY(IArtist)
END_COM_MAP()
…
HRESULT ArtistDraw();
HRESULT CowboyDraw();
};
Credit
Tim Ewald showed me this trick years ago. Jim Springfield showed me how it
could be used with ATL. Don Box recommended the ATL_NO_VTABLE optimization.
Copyright
This web page is adapted from the book
ATL Internals by Brent Rector
and Chris Sells. Copyright � 1998
by Addison Wesley Longman, Inc. All rights reserved.