Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
Using standard multiple inheritance to support COM interfaces is simple: derive and implement. The compiler and linker will automatically direct each interface's IUnknown methods to a common, shared implementation. This behavior is great for IUnknown, but not so great if we have a coclass deriving from two (or more) interfaces with the same method names. For example, let's say a colleague has just designed a new COM interface that draws any shape with a 3-D look and feel:
// I3DRender [object, uuid(DD900360-2C02-11d3-B901-0020781238D4)] interface I3DRender : IUnknown { HRESULT Draw(); };
This new interface gets us excited, and we want a shape to render itself in both 2-D as well as vivid 3-D. Using multiple inheritance, we design Co3DHexagon accordingly:
// This shape can be drawn in 2-D or 3-D. class Co3DHexagon : public IDraw, public I3DRender { public: Co3DHexagon(); virtual ~Co3DHexagon(); // IUnknown methods. // IDraw methods. // I3DRender methods. ... };
As you know by now, IDraw defines the sole method Draw(). I3DRender also defines a single method named Draw(). Therefore, the C++ compiler and linker will, again, direct both Draw() methods to the same implementation. In this case, we don't appreciate the compiler and linker as much as we did when they shared the common methods of IUnknown. However, just as we can use nested classes to provide a specific implementation for an interface's IUnknown we can also use nested classes to resolve name clashes. Whenever you have a COM interface containing methods with the same signature of another interface supported by the coclass, create a nested class to resolve the name clash.
For example, to allow Co3DHexagon to provide unique implementations for each Draw() method, we begin by deriving from each custom interface excluding the offending I3DRender. Next, we define an inner class to represent the implementation of I3DRender, setting its back pointer in Co3DHexagon's constructor:
// Using a nested class to resolve interface method name clashes. class Co3DHexagon : public IDraw // Not deriving from I3DRender... { public: Co3DHexagon() { m_refCount = 0; m_3dRenderImpl.m_pParent = this; } virtual ~Co3DHexagon(); // Master IUnknown STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID riid, void** ppv); // IDraw::Draw STDMETHODIMP Draw(); // I3DRender is represented by a nested class. struct I3DRenderImpl : public I3DRender { // IUnknown methods. STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID riid, void** ppv); // I3DRender methods. STDMETHODIMP Draw(); // Pointer to parent. Co3DHexagon* m_pParent; }; private: // Instance of inner class. I3DRenderImpl m_3dRenderImpl; ULONG m_refCount; };
The outer class's IUnknown::QueryInterface() will provide a static cast to IUnknown and IShapeEdit, but return a reference to m_3dRenderImpl if asked for I3DRender:
// Master QueryInterface() logic. STDMETHODIMP Co3DHexagon::QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown) { *ppv = (IUnknown*)(IDraw*)this; } else if (riid == IID_IDraw) { *ppv = (IDraw*)this; } else if(riid == IID_I3DRender) { *ppv = (I3DRender*)&m_3dRenderImpl; } else { *ppv = NULL; return E_NOINTERFACE; } ((IUnknown*)(*ppv))->AddRef(); return S_OK; }
To finish up our inner class, we will use double scope resolution to provide implementations of IUnknown (which as before simply forwards the call to Co3DHexagon's implementation) as well as an implementation of I3DRender::Draw():
// I3DHexagon::Draw() STDMETHODIMP Co3DHexagon::I3DRenderImpl::Draw() { MessageBox(NULL, "Drawing a lovely 3D hexagon", "Inner Class", MB_OK | MB_SETFOREGROUND); return S_OK; }
IDraw's version of Draw() will be implemented as usual:
// IDraw::Draw() STDMETHODIMP Co3DHexagon::Draw() { MessageBox(NULL, "Drawing a boring 2D hexagon", "Outer Class", MB_OK | MB_SETFOREGROUND); return S_OK; }
Again, the COM client has no idea of our C++ handiwork:
' A simple VB client using a coclass ' constructed with nested class composition. ' Private Sub Form_Load() Dim threeDHex As New Co3DHexagon ' Draw in 2D. threeDHex.Draw ' Now draw in 3D Dim itf3D As I3DRender Set itf3D = threeDHex itf3D.Draw End Sub
On the CD If you would like to investigate the code behind a raw C++ name clash, your CD-ROM contains the Co3DHexagon project under the Labs\Chapter 08\RawNameClash subdirectory.
| < Free Open Study > |
|