Variations on the ATL COM Map Macros

 < Free Open Study > 



<atlcom.h> defines the entire set of COM map macros provided by ATL 3.0. If you examine these macros, you will see a total of 17 different variations. Don't lose heart, however, as many of these macros are grouped by related functionality. For example, four of the macros deal with COM aggregation. Three are used to resolve base interface ambiguities. That brings our count down to ten. The story gets brighter still, as two of the COM map macros are no longer necessary as of ATL 3.0:

// The 'impl' macros are now obsolete as of ATL 3.0. #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>)

These macros were used in ATL 2.0 as a way to provide debugging support for interface reference counting; however, interface debugging has changed with the release of ATL 3.0. Legacy ATL code that makes use of these older COM map macros will still compile just fine under ATL 3.0, as the _IMPL macros are redefined to the more useful COM_INTERFACE_ENTRY_IID.

The remaining COM map macros will be examined throughout this chapter, as well as suggestions on when they might actually be useful. To help you keep your bearings, the following table lists the full set of ATL COM macros, grouped by related functionality:

ATL COM Map Macro

Meaning in Life

COM_INTERFACE_ENTRY

The most common macro. Used for standard interface support.

COM_INTERFACE_ENTRY_IID COM_INTERFACE_ENTRY2 COM_INTERFACE_ENTRY2_IID

Used to resolve ambiguities in your interface hierarchies.

COM_INTERFACE_ENTRY_AGGREGATE COM_INTERFACE_ENTRY_AGGREGATE_BLIND COM_INTERFACE_ENTRY_AUTOAGGREGATE COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND

This set is used to allow an outer coclass to reuse inner coclasses via COM aggregation.

COM_INTERFACE_ENTRY_TEAR_OFF COM_INTERFACE_ENTRY_CACHED_TEAR_OFF

These macros are used to support tear-off interfaces.

COM_INTERFACE_ENTRY_NOINTERFACE

Returns E_NOINTERFACE for the specified IID. Useful if you want to automatically exclude an interface from your coclass.

COM_INTERFACE_ENTRY_BREAK

Forces a break to occur during a debug cycle if this interface is queried for.

COM_INTERFACE_ENTRY_CHAIN

Allows a subclass to "inherit" the COM map of a base class.

COM_INTERFACE_ENTRY_FUNC COM_INTERFACE_ENTRY_FUNC_BLIND

Used to force ATL to jump to a custom function when a given interface is queried for. These macros allow you to extend the ATL COM map to your liking.

Implementing a Coclass Using C++ Multiple Inheritance

To get to know these new COM map macro entries, we first need to understand the pros and cons of standard C++ multiple inheritance (MI). All of the COM objects developed thus far have had one thing in common: heavy use of standard C++ multiple inheritance. If we were to create a class declaration for the following COM object (Figure 8-1):

click to expand
Figure 8-1: A familiar- looking coclass, exposing three interfaces.

We could create a C++ header file representing this notation as so:

// Building CoHexagon using C++ multiple inheritance. class CoHexagon : public IDraw, public IShapeEdit { public:      CoHexagon();      virtual ~CoHexagon();      // IUnknown methods.      // IDraw methods.      // IShapeEdit methods. private:      ULONG m_refCount; };

As you know, IUnknown is the only COM compliant interface which does not derive from any other interface. Every standard and custom interface beyond IUnknown must derive either directly or indirectly from IUnknown, and thus ensures the first three members of an interface's vTable will be the first three methods of IUnknown. But have you stopped to notice that although every COM interface derives from IUnknown, we only need supply a single implementation of QueryInterface(), AddRef(), and Release() for a given coclass?

Given the current incarnation of CoHexagon, it looks as if we have brought in the methods of IUnknown twice: once for IDraw and another time for IShapeEdit. How then can we provide a single implementation of IUnknown in the coclass without causing a number of unresolved external compiler errors?

The best thing about using standard C++ multiple inheritance to support numerous COM interfaces is that the C++ compiler and linker will politely fill the vTable entries for each interface to point to the same (shared) implementation of IUnknown. Typically this is exactly what we want. When using MI, we ensure that all of our COM compliant interfaces can share the same implementation of IUnknown. For example:

// Each calls CoHexagon::QueryInterface() pDraw -> QueryInterface(IID_IShapeEdit, (void**)&pShapeEdit); pShapeEdit -> QueryInterface(IID_IUnknown, (void**)&pUnk); pShapeEdit -> Release();     // Each calls CoHexagon::Release() pUnk -> Release(); pDraw -> Release();

Implementing a COM Object Using Nested Classes

Multiple inheritance is not the only way to build a coclass. Other frameworks, such as MFC, support multiple interfaces on a coclass using a technique called nested classes. Here, the coclass under construction (that would be CoHexagon) derives from IUnknown exclusively. A unique inner class represents an interface the outer class wishes to support. While MFC provides a number of framework-specific macros to facilitate writing inner classes, we can do the same in straight C++. To create CoHexagon using nested classes, we begin by deriving the outer class from IUnknown:

// The outer class derives from IUnknown, and provides the // master implementation for all inner classes. class CoHexagon : public IUnknown { public:      CoHexagon();      virtual ~CoHexagon();      // Master IUnknown methods.      STDMETHODIMP_(ULONG) AddRef();      STDMETHODIMP_(ULONG) Release();      STDMETHODIMP QueryInterface(REFIID riid, void** ppv); private:      ULONG m_refCount; }; 

The implementation of AddRef() and Release() for the outer class (CoHexagon) would be as usual; simply adjust the internal reference count and check for the final release:

// The use of nested classes will not affect the outer class's reference counting logic. STDMETHODIMP_(ULONG) CoHexagon::AddRef() {      return ++m_refCount; } STDMETHODIMP_(ULONG) CoHexagon::Release() {      if(--m_refCount == 0)      {           delete this;           return 0;      }      return m_refCount; }

QueryInterface(), however, is implemented differently for an outer class making use of nested classes (we will see exactly how so momentarily).

Defining an Inner Class

When you wish to define an inner class to represent an implemented interface, declare the class definition directly within the outer class. Each inner class will derive directly from the interface it represents, and provide implementations for each method of the custom interface, as well as its own implementation of IUnknown. The good news is that inner classes can instead call the master IUnknown methods on the outer object to do most of the real work. Allowing the inner class to communicate with the outer class requires that each inner class maintain a pointer back to its parent. This back pointer can be represented as a member variable of the inner class. Here, then, is CoHexagon supporting IDraw as a nested class:

// CoHexagon using nested classes as opposed to MI. class CoHexagon : public IUnknown { public: ...      // IDrawImpl is a nested class.      struct IDrawImpl : public IDraw      {           // Inner IUnknown methods.           STDMETHODIMP_(ULONG) AddRef();           STDMETHODIMP_(ULONG) Release();           STDMETHODIMP QueryInterface(REFIID riid, void** ppv);           // IDraw methods.           STDMETHODIMP Draw();           // Pointer to parent.           CoHexagon* m_pParent; }; // Master IUnknown methods. STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID riid, void** ppv); private:      ULONG m_refCount;      IDrawImpl m_drawImpl;     // Hold onto instance of inner class. }; 

As you can see, the outer class maintains a member variable for each inner class it maintains (currently IDrawImpl). When the outer class is created, it sets the back pointer of the nested class. Assume CoHexagon defines another inner class (IShapeEditImpl) to support the IShapeEdit interface. During construction, CoHexagon tells each inner object how to report back to it:

// The outer object sets the back pointer during construction. CoHexagon::CoHexagon() {      m_refCount = 0;      // Tell each nested class who the parent is (set the back pointer).      m_drawImpl.m_pParent = this;      m_shapeEditImpl.m_pParent = this; }

The reason inner classes need to point back to the parent becomes clear when we examine how to implement the IUnknown interface for each inner object: Inner objects use this back pointer to call the master IUnknown implementation. Furthermore, if the outer class defines public helper methods (which are not rigged up to a COM interface, but simply exist to help the class get work accomplished) the inner objects may call these helper functions using the same back pointer.

Implementing an Inner Class

When implementing the methods of an inner nested class, we must make use of double scope resolution syntax. Recall that a standard C++ class uses the scope resolution operator to implement the methods prototyped in its header file, using the following syntax:

// ReturnType ClassName::FunctionName() ULONG CoHexagon::AddRef(){...}

When implementing a nested class, the inner must be scoped to the context of the outer. For example:

// ReturnType OuterClassName::InnerClassName::FunctionName() ULONG CoHexagon::IDrawImpl::AddRef(){...}

Here then, is the implementation for the inner IDrawImpl nested class. Notice that the inner object's IUnknown implementation simply calls back to the parent class using the m_pParent pointer. The implementation of the Draw() method is as expected.

// IDrawImpl implements IUnknown using the parent pointer. STDMETHODIMP_(ULONG) CoHexagon::IDrawImpl::AddRef() {      return m_pParent->AddRef(); } STDMETHODIMP_(ULONG) CoHexagon::IDrawImpl::Release() {      return m_pParent->Release(); } STDMETHODIMP CoHexagon::IDrawImpl::QueryInterface(REFIID riid, void** ppv) {      return m_pParent->QueryInterface(riid, ppv); } STDMETHODIMP CoHexagon::IDrawImpl::Draw() {      MessageBox(NULL, "CoHexagon::IDrawImpl::Draw", "Inner Class",                MB_OK | MB_SETFOREGROUND);      return S_OK; }

IShapeEditImpl would be implemented using the same approach. Use the back pointer to implement IUnknown and provide code for each custom method of IShapeEdit.

Implementing the Outer QueryInterface()

The last step is to finish the IUnknown implementation for the outer class. As we have seen, AddRef() and Release() need no modifications whatsoever for an outer class. QueryInterface() will be modified such that if the client asks for any interface other than IUnknown, we hand off a reference to the correct inner class:

// QueryInterface hands off interface pointers as always, // this time using the inner members. STDMETHODIMP CoHexagon::QueryInterface(REFIID riid, void** ppv) {      if (riid == IID_IUnknown)      {           *ppv = (IUnknown*)this;      }      else if (riid == IID_IDraw)      {           *ppv = (IDraw*)&m_drawImpl;      }      else if(riid == IID_IShapeID)      {           *ppv = (IShapeID*)&m_shapeIDImpl;      }      else      {           *ppv = NULL;           return E_NOINTERFACE;      }      ((IUnknown*)(*ppv))->AddRef();      return S_OK; }

Making Use of a Nested Class

A COM client could care less that the coclass has chosen to supply interface pointers using nested classes as opposed to standard multiple inheritance. Here is a Visual Basic client obtaining access to the [default] IDraw interface and asking for IShapeEdit:

' A VB client using a coclass ' constructed with nested class composition. ' Private Sub Form_Load()      ' Make outer object and get IDraw.      Dim hex As New CoHexagon      hex.Draw      ' Query for IID_IShapeEdit      Dim itfSE As IShapeEdit      Set itfSE = hex      itfSE.Invert End Sub

Nested classes provide an alternative to standard C++ multiple inheritance. If you are using MFC to build custom COM objects, this is the road you tend to take. As you have just seen, inner classes derive from the COM interface that they represent, which entails a unique implementation of IUnknown for each inner class. The outer class assembles QueryInterface() to hand off references to the inner class instances.

I am sure you all agree that multiple inheritance is a far simpler approach. So why then did we even bother to examine nested classes? Nested classes can be used to resolve method name clashes. Before we see how nested classes can help with this issue, the next lab gives you a chance to work with nested classes firsthand.

Lab 8-1: Building a Coclass with Nested Classes

Although C++ multiple inheritance is a far simpler approach than nested classes, both techniques yield the same result of a COM-compliant binary layout. In this lab you will be given the chance to assemble an in-proc server that makes use of nested classes. Beyond building character, this lab will get you into the necessary mindset to understand how the nested class technique allows you to resolve interface method name clashes in a coclass.

 On the CD   The solution for this lab can be found on your CD-ROM under:
Labs\Chapter 08\RawNested
Labs\Chapter 08\RawNested\VB Client

Step One: Develop the Initial Project and IDL

The goal of this lab is to let you build a coclass that implements a set of interfaces using nested classes. If you are up to the challenge, begin by creating a brand new Win32 DLL project named RawNested (an empty DLL is fine).

Insert a new file named rawnested.idl and be sure you deselect the MkTypLib Compatible option from your project settings. In your new IDL file, define your good friend, IDraw. Next, define an interface named IShapeID. This interface allows you to refer to your shapes by a numerical identifier. Finally, define the CoHexagon coclass that supports each interface. Here is the complete IDL code:

// Bring in core IDL files. import "oaidl.idl"; // IDraw [object, uuid(A533DA31-D372-11d2-B8CF-0020781238D4)] interface IDraw : IUnknown {      HRESULT Draw(); }; // IShapeID [object, uuid(DD900363-2C02-11d3-B901-0020781238D4)] interface IShapeID : IUnknown {      [propput] HRESULT ID([in] int ShapeId);      [propget] HRESULT ID([out, retval] int* ShapeId); }; // Library statement. [uuid(AD454EA3-2C15-11d3-B901-0020781238D4), version(1.0), helpstring("CoHexagon nesting others")]] library RawNested {      importlib("stdole32.tlb");      [uuid(AD454EA0-2C15-11d3-B901-0020781238D4)]      coclass CoHexagon      {           [default] interface IDraw;           interface IShapeID;      }; };

Insert this file into your project workspace and compile your IDL file to ensure there are no syntactical problems.

Step Two: Implement CoHexagon Using Nested Classes

CoHexagon is a COM object supporting the IDraw and IShapeID interfaces, this time using nested classes rather than C++ MI. Begin by defining the class definition of CoHexagon. Recall that outer classes derive directly from IUnknown:

// CoHexagon using nested classes as opposed to MI. class CoHexagon : public IUnknown { public:      CoHexagon();      virtual ~CoHexagon();      // Master IUnknown implementation.      STDMETHODIMP_(ULONG) AddRef();      STDMETHODIMP_(ULONG) Release();      STDMETHODIMP QueryInterface(REFIID riid, void** ppv); private:      ULONG m_refCount; };

Implement your AddRef() and Release() methods as usual, remembering to set your internal reference counter (m_refCount) to zero in the constructor of CoHexagon.

Next, we will be adding support for IDraw using a nested class. Inside the definition of CoHexagon, define an inner class deriving from IDraw, named IDrawImpl. Also define a CoHexagon pointer member variable in the inner class, allowing the child class to communicate with the parent:

// Inner classes maintain a pointer to the parent, for use in its own // IUnknown implementation. class CoHexagon : public IUnknown { public:      CoHexagon();      virtual ~CoHexagon();      // This inner class implements IDraw.      struct IDrawImpl : public IDraw      {           // IUnknown methods.           STDMETHODIMP_(ULONG) AddRef();           STDMETHODIMP_(ULONG) Release();           STDMETHODIMP QueryInterface(REFIID riid, void** ppv);           // IDraw methods.           STDMETHODIMP Draw();           // Pointer to parent.           CoHexagon* m_pParent;      }; ... }:

Now do the same for IShapeID using another nested class called IShapeIDImpl:

// The second inner class, also defined within the scope of CoHexagon. struct IShapeIDImpl : public IShapeID {      // IUnknown methods.      STDMETHODIMP_(ULONG) AddRef();      STDMETHODIMP_(ULONG) Release();      STDMETHODIMP QueryInterface(REFIID riid, void** ppv);      // IShapeID methods.      STDMETHODIMP put_ID(int id);      STDMETHODIMP get_ID(int* id);      // Pointer to parent.      CoHexagon* m_pParent; };

Before we begin implementing the inner methods of each nested class, let's finish up the definition of CoHexagon by declaring an instance of both IDrawImpl and IShapeIDImpl, and set each m_pParent pointer in CoHexagon's constructor:

// In the constructor of the outer class, set each inner class's back pointer. m_drawImpl.m_pParent = this; m_shapeIDImpl.m_pParent = this;

Now, we can complete the implementation of CoHexagon's IUnknown. In your QueryInterface() method, test the incoming REFIID parameter and return IUnknown, IDraw, and IShapeID pointers. In the case of the nested classes, stuff the (void**) parameter with a reference to the correct inner object:

// The outer class's QI hands out references to the inner objects. STDMETHODIMP CoHexagon::QueryInterface(REFIID riid, void** ppv) {      if (riid == IID_IUnknown)      {           *ppv = (IUnknown*)this;      }      else if (riid == IID_IDraw)      {           *ppv = (IDraw*)&m_drawImpl;      }      else if(riid == IID_IShapeID)      {           *ppv = (IShapeID*)&m_shapeIDImpl;      }      else      {           *ppv = NULL;           return E_NOINTERFACE;      }      ((IUnknown*)(*ppv))->AddRef();      return S_OK; } 

To finish up the implementation of CoHexagon, we need to implement IDrawImpl as well as IShapeIDImpl. Start by implementing IUnknown for IDrawImpl. Recall that your inner classes maintain a pointer back to the parent, which allows it to call any method in the parent's public sector. Use this back pointer to flesh out the details of AddRef(), Release(), and QueryInterface() using double scope resolution syntax:

 // IDrawImpl STDMETHODIMP_(ULONG) CoHexagon::IDrawImpl::AddRef() {      return m_pParent->AddRef(); } STDMETHODIMP_(ULONG) CoHexagon::IDrawImpl::Release() {      return m_pParent->Release(); } STDMETHODIMP CoHexagon::IDrawImpl::QueryInterface(REFIID riid, void** ppv) {      return m_pParent->QueryInterface(riid, ppv); }

As for Draw() itself, I still always recommend calls to MessageBox:

// Draw your inner object. STDMETHODIMP CoHexagon::IDrawImpl::Draw() {      MessageBox(NULL, "CoHexagon::IDrawImpl::Draw", "Inner Class",                MB_OK | MB_SETFOREGROUND );      return S_OK; }

To implement IShapeIDImpl, start by adding a public integer data member to CoHexagon named m_ID, and set it to zero in the constructor. This will be used by the put_ID and get_ID methods of IShapeID. Next, implement the IUnknown methods for IShapeIDImpl in the same way you did for IDrawImpl. As IShapeID defines a single property, we have two final methods to implement. Here is the code for get_ID and put_ID. Notice the use of the back pointer:

// IShapeIDImpl STDMETHODIMP CoHexagon::IShapeIDImpl::put_ID(int shapeID) {      m_pParent->m_ID = shapeID;      // Set parent's m_ID.      return S_OK; } STDMETHODIMP CoHexagon::IShapeIDImpl::get_ID(int* shapeID) {      *shapeID = m_pParent->m_ID;      // Get parent's m_ID.      return S_OK; } 

Recall that the m_ID data member defined in CoHexagon is public and therefore we can access it directly from the scope of an inner class. If you would rather, feel free to create your own accessor and mutator for a private m_ID, or declare a friendship (e.g., the friend keyword) between the outer and inner classes. Regardless of which path you choose, you should see how to equip an inner class to communicate to the outer parent.

Go ahead and compile.

Step Three: Build a Class Factory

Developing a class factory for your COM objects is a very redundant operation. Feel free to steal the files from a previous raw C++ project. As you know, a typical class factory derives from IClassFactory. In your CreateInstance() implementation, create a new CoHexagon as usual:

// Yet another class factory. STDMETHODIMP CoHexFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void** ppv) {      if(pUnkOuter != NULL)           return CLASS_E_NOAGGREGATION;      CoHexagon* pHexObj = NULL;      HRESULT hr;      // Create the object.      pHexObj = new CoHexagon;      hr = pHexObj -> QueryInterface(riid, ppv);      if (FAILED(hr))           delete pHexObj;      return hr; }

To implement LockServer(), you will need to add a global lock counter, which you may assume for the moment is defined in another file. Likewise, be sure the constructor and destructor of the class factory adjust the server-wide object count. AddRef(), Release(), and QueryInterface() are the same as they would be for any class factory. Finally, be sure to adjust the object counter in the constructor and destructor of CoHexagon.

Step Four: Assemble the Component Housing

Like class factories, in-proc server export functions are also very boilerplate in nature. Feel free to reuse a previous implementation. You know the drill:

// Exports. STDAPI DllCanUnloadNow() {      if(g_lockCount == 0 && g_objCount == 0)           return S_OK;          // Unload me.      else           return S_FALSE;       // Keep me alive. } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) {      HRESULT hr;      CoHexFactory *pCFact = NULL;      // We only know how to make CoHexagon objects in this house.      if(rclsid == CLSID_CoHexagon) {           pCFact = new CoHexFactory;           hr = pCFact -> QueryInterface(riid, ppv);           if(FAILED(hr))                delete pCFact;      }      else           return CLASS_E_CLASSNOTAVAILABLE;      return hr; } 

Finally, create (and insert) a DEF file into the project and build (and merge) a minimal REG file specifying the ProgID and CLSID for CoHexagon. Go ahead and compile your new DLL. You should come through nice and clean.

Step Five: A Simple Visual Basic Client

Fire up VB, set a reference to your server's type library, and create a simple client to test your server. Figure 8-2 shows a possible sample GUI:


Figure 8-2: Making use of nested classes.

Here is the VB code that exercises CoHexagon. Notice that the client has no idea the coclass is composed of nested classes rather than C++ multiple inheritance:

' [General][Declarations] ' Option Explicit Private mTheHex As CoHexagon Private mItfShapeID As IShapeID Private Sub btnCreate_Click()      ' Make the Hex.      Set mTheHex = New CoHexagon      ' Set ID.      Set mItfShapeID = mTheHex      mItfShapeID.ID = CInt(txtID.Text)      ' Enable buttons      btnDraw.Enabled = True      btnID.Enabled = True End Sub Private Sub btnDraw_Click()      ' Draw the hex.      mTheHex.Draw End Sub Private Sub btnID_Click()      ' Echo ID.      MsgBox mItfShapeID.ID, , "The ID is..." End Sub Private Sub Form_Load()      btnDraw.Enabled = False      btnID.Enabled = False End Sub 

So, now that you have written a coclass using nested classes, you may be wondering exactly why one might want to do this. After all, nested classes entail more work than multiple inheritance. Well, read on and let's examine one situation where C++ multiple inheritance can leave much to be desired, and how the nested class technique can be used to remedy the situation.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

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