Forwarding Shims

Home

The Forwarding Shims Problem 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:

class CAcePowell : public CComObjectRootEx,
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, 
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 andforward 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, 
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,
public _ICowboy,
public _IArtist
{
public:
BEGIN_COM_MAP(CAcePowell)
COM_INTERFACE_ENTRY(ICowboy)
COM_INTERFACE_ENTRY(IArtist)
END_COM_MAP()

...

HRESULT ArtistDraw();
HRESULT CowboyDraw();
};