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 performthat 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:

#define COM_INTERFACE_ENTRY_IID(iid, x) \                         { &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:

interface IGlobe : ISphere {}; interface 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:

class CDesktopGlobe :   public CComObjectRootEx<CDesktopGlobe>,   public IGlobe,   public IPlanet { public:   ... BEGIN_COM_MAP(CDesktopGlobe)   COM_INTERFACE_ENTRY(ISphere) // ambiguous   COM_INTERFACE_ENTRY(IGlobe)   COM_INTERFACE_ENTRY(IPlanet) END_COM_MAP()   // ISphere methods   ...   // IGlobe methods   ...   // IPlanet methods   ... }; 


The problem is more easily seen when you look at the inheritance hierarchy in Figure 6.1.

Figure 6.1. CDesktopGlobe inheritance hierarchy


Figure 6.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:

class CDesktopGlobe :   public CComObjectRootEx<CDesktopGlobe>,   public IGlobe,   public IPlanet { public: ... BEGIN_COM_MAP(CDesktopGlobe)   COM_INTERFACE_ENTRY_IID(IID_ISphere, IGlobe) // unambiguous   COM_INTERFACE_ENTRY(IGlobe)   COM_INTERFACE_ENTRY(IPlanet) END_COM_MAP() ... }; 


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:

template <typename Base> class ISphereImpl : public Base {...}; 


Using ISphereImpl looks like this:

class CDesktopGlobe :   public CComObjectRootEx<CDesktopGlobe>,   public ISphereImpl<IGlobe>,   public ISphereImpl<IPlanet> { public: ... BEGIN_COM_MAP(CDesktopGlobe)   COM_INTERFACE_ENTRY_IID(IID_ISphere, IGlobe) // Default ISphere   COM_INTERFACE_ENTRY(IGlobe)   COM_INTERFACE_ENTRY(IPlanet) END_COM_MAP() ... }; 


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:

#define COM_INTERFACE_ENTRY2(x, x2)\                   { &_ATL_IIDOF(x),\                                     reinterpret_cast<DWORD_PTR>( \                         static_cast<x*>( \                                     static_cast<x2*>( \                                    reinterpret_cast<_ComMapClass*>(8))))-8, \     _ATL_SIMPLEMAPENTRY},                            #define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\          { &iid,\                                               reinterpret_cast<DWORD_PTR>( \                         static_cast<x*>( \                                     static_cast<x2*>( \                                    reinterpret_cast<_ComMapClass*>(8))))-8, \       _ATL_SIMPLEMAPENTRY},                          


COM_INTERFACE_ENTRY2 is much like COM_INTERFACE_ENTRY_IID because it enables you to resolve the problem of multiple bases:

class CDesktopGlobe :   public CComObjectRootEx<CDesktopGlobe>,   public IGlobe,   public IPlanet { public: ... BEGIN_COM_MAP(CDesktopGlobe)   COM_INTERFACE_ENTRY2(ISphere, IGlobe) // Use the IGlobal branch   COM_INTERFACE_ENTRY(IGlobe)   COM_INTERFACE_ENTRY(IPlanet) END_COM_MAP() ... }; 


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 to follow 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:

class CDesktopGlobe :   public CComObjectRootEx<CDesktopGlobe>,   public IGlobe,   public IPlanet { public: ... BEGIN_COM_MAP(CDesktopGlobe)   COM_INTERFACE_ENTRY2_IID(&IID_ISphere, ISphere, IGlobe)   COM_INTERFACE_ENTRY(IGlobe)   COM_INTERFACE_ENTRY(IPlanet) END_COM_MAP() ... }; 


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:

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() ...   STDMETHODIMP Draw() { /* Act as a cowboy or an artist? */ } }; 


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:

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() ...   STDMETHODIMP IArtist::Draw() {     /* Draw like an artist */     return S_OK;   }   STDMETHODIMP ICowboy::Draw() {     /* Draw like a cowboy */     return S_OK;   } }; 


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]

[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:

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. Because both shims derive from the interface in question, the 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() ...   STDMETHODIMP ArtistDraw();   STDMETHODIMP CowboyDraw(); }; 


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]:

[4] Don Box suggested the final efficiency trick, the use of ATL_NO_VTABLE.

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(); }; 


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:

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 { public: BEGIN_COM_MAP(CAcePowell)   COM_INTERFACE_ENTRY(ICowboy)   COM_INTERFACE_ENTRY(IArtist) END_COM_MAP() ...   HRESULT Draw();       // Use for both IArtist::Draw and                         // ICowboy::Draw   HRESULT CowboyDraw(); // Never called! }; 


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:

struct ISphere : IUnknown {   STDMETHOD(Rotate)(long nDegrees, long* pnOrientation) =0;   STDMETHOD(Twirl)(long nVelocity) =0; }; struct IRedSphere {   // Colored IUnknown methods   STDMETHOD(RedQueryInterface)( REFIID riid, void** ppv) =0;   STDMETHOD_(ULONG, RedAddRef)() =0;   STDMETHOD_(ULONG, RedRelease)() =0;   // Uncolored ISphere methods   STDMETHOD(Rotate)(long nDegrees, long* pnOrientation) =0;   STDMETHOD(Twirl)(long nVelocity) =0; }; 


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:

class CDesktopGlobe :   public CComObjectRootEx<CComSingleThreadModel>,   public IRedSphere,   public IGlobe,   public IPlanet { public:   ... BEGIN_COM_MAP(CDesktopGlobe)   // Expose IRedShere when ISphere is requested   COM_INTERFACE_ENTRY_IID(IID_ISphere, IRedSphere)   COM_INTERFACE_ENTRY(IGlobe)   COM_INTERFACE_ENTRY(IPlanet) END_COM_MAP()   ...   // Colored method implementations   STDMETHODIMP RedQueryInterface(REFIID riid, void** ppv)   { return GetUnknown()->QueryInterface(riid, ppv); }   STDMETHODIMP_(ULONG) RedAddRef() {     _ThreadModel::Increment(&m_cRefSphere);     return GetUnknown()->AddRef();   }   STDMETHODIMP_(ULONG) RedRelease() {     _ThreadModel::Decrement(&m_cRefSphere);     return GetUnknown()->Release(); } private:   long m_cRefSphere; }; 


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.

void TryRotate(IUnknown* punk) {   ISphere* ps = 0;   // Implicit AddRef really a call to RedAddRef   if(SUCCEEDED(punk->QueryInterface(IID_ISphere, (void**)&ps))) {     // ps actually points to an IRedSphere*     ps->Rotate();     ps->Release(); // Really a call to RedRelease   } } 


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:

template <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:

#define COM_INTERFACE_ENTRY_IMPL(x) \                             COM_INTERFACE_ENTRY_IID(_ATL_IIDOF(x), x##Impl<_ComMapClass>)    #define COM_INTERFACE_ENTRY_IMPL_IID(iid, x) \                    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.

[5] The _ALT_DEBUG_INTERFACES macro provides this service and is discussed in Chapter 4, "Objects in ATL."




ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net