Resolving Name Clashes in ATL

 < Free Open Study > 



Let's examine the same problem of clashing method names in ATL. To begin, assume we have an in-proc server named ATLNameClash, containing Co3DHexagon which supports the default IDraw interface. Next, assume we have written the IDL code for I3DRender in our project's IDL file and added a single method named Draw(). If we were to now use the Implement Interface Wizard, ATL would do the following:

  • Add I3DRender to our inheritance chain.

  • Add a COM_INTERFACE_ENTRY for I3DRender.

What ATL would not do is create a nested class for I3DRender::Draw(). In fact, as IDraw already defines a Draw() method, ATL does not write a single line of stub code. If we were to compile and test the project, a COM client would indeed be able to ask for I3DRender and IDraw independently; however, each Draw() method would call the shared implementation. Here is the story thus far:

// ATL class with unique methods mapping to a shared implementation. class ATL_NO_VTABLE CCo3DHexagon :      public CComObjectRootEx<CComSingleThreadModel>,      public CComCoClass<CCo3DHexagon, &CLSID_Co3DHexagon>,      public IDraw,      public I3DRender { public: ... BEGIN_COM_MAP(CCo3DHexagon)      COM_INTERFACE_ENTRY(IDraw)      COM_INTERFACE_ENTRY(I3DRender) END_COM_MAP() public:      STDMETHOD(Draw)(); }; // Shared implementation. STDMETHODIMP CCo3DHexagon::Draw() {      MessageBox(NULL, "I don't know what to do!!", "Panic!", MB_OK);      return S_OK; }

Remedying the name clash in ATL will require a similar but not completely identical approach to the nested class example used in straight C++. The reason we cannot just carry over the previous example has to do with how ATL handles QueryInterface(). The COM map is ATL's way to provide a table driven approach to return interfaces to clients. While this works great, it does hide the standard implementation of QueryInterface() we know and love (if...else...if...else). Therefore, we have no obvious way to tweak the QueryInterface() logic to hand out references to a nested class.

In ATL, we can resolve method name clashes by creating wrapper classes around the offending interfaces, and intercept the ambiguity before our coclass is aware the clash exists. The trick is to provide an implementation of the clashing methods within the wrapper, which in turn call unique methods on the supporting class. Our first step, then, is to create the wrapper classes. Assume you have inserted a new file (wrappers.h) into your project and define the following:

// Here we provide an implementation of I3DRender::Draw(), which calls // a uniquely named method in the coclass (the name of the method is arbitrary). // Note the wrapper methods are pure virtual, as the coclass will provide a distinct // implementation. struct I3DRenderImpl : public I3DRender {      STDMETHODIMP Draw()      {           return Draw3DHex();      }      STDMETHOD(Draw3DHex)() = 0; }; struct IDrawImpl : public IDraw {      STDMETHODIMP Draw()      {           return Draw2DHex();      }      STDMETHOD(Draw2DHex)() = 0; };

Note 

Recall that the STDMETHOD macro inserts the virtual keyword, while STDMETHODIMP does not. Do not use STDMETHODIMP for the uniquely named methods, as these will be implemented by the coclass, and must be pure virtual.

We can now go back to the ATL coclass, and rather than deriving from IDraw and I3DRender directly, derive from the wrapper classes.

// Deriving from the wrapper classes will not affect your COM map. class ATL_NO_VTABLE CCo3DHexagon :      public CComObjectRootEx<CComSingleThreadModel>,      public CComCoClass<CCo3DHexagon, &CLSID_Co3DHexagon>,      public IDrawImpl,      public I3DRenderImpl { ... // COM map stays the same. BEGIN_COM_MAP(CCo3DHexagon)      COM_INTERFACE_ENTRY(IDraw)      COM_INTERFACE_ENTRY(I3DRender) END_COM_MAP() ... public:      // These are inherited from the wrappers.      STDMETHOD(Draw2DHex)();      STDMETHOD(Draw3DHex)(); ... };

Now, if a client calls Draw() from an IDraw pointer, the wrapper class (IDrawImpl) in turn calls a unique method by the coclass. Likewise for I3DRenderImpl. Although each interface has a Draw() method, the name clash is avoided as they have each provided an implementation in a separate wrapper class, not twice in a single class. All we have left to do is flesh out the pure virtual methods in our derived class:

// To each their own. STDMETHODIMP CCo3DHexagon::Draw3DRect() {      MessageBox(NULL, "3D!!", "Better!", MB_OK | MB_SETFOREGROUND);      return S_OK; } STDMETHODIMP CCo3DHexagon::Draw2DRect() {      MessageBox(NULL, "2D!!", "Better!", MB_OK | MB_SETFOREGROUND);      return S_OK; }

When you are writing your wrapper classes for conflicting interfaces, you only need to forward the clashing methods to a unique member in the coclass. In other words, if IDraw and I3DRender each had additional members that do not introduce a name clash:

// Each interface has an offending method as well as a non-offending method. interface IDraw : IUnknown {      HRESULT Draw();                    // Offensive.      HRESULT AnotherIDrawMethod();     // Non-offensive. }; interface I3DRender : IUnknown {      HRESULT Draw();                    // Offensive.      HRESULT AnotherI3DRenderMethod();     // Non-offensive. };

Your wrappers will only need to account for Draw(). The non-offending methods would be implemented as usual in the Co3DHexagon coclass.

Lab 8-2: Dealing with Interface Name Clashes in ATL

As you develop more sophisticated coclasses, you are bound to run into the situation where two interfaces each define a method with the same name and signature. This lab will give you the chance to explore some ways to circumvent this sort of name clash using ATL.

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

Step One: Create a Name Clash

Before we create wrapper classes for offending interface method names, we need to create a name clash to illustrate the problem. Begin your lab by accessing the ATL COM AppWizard. Create an in-proc server named ATLNameClash. Open the Object Wizard and insert a Simple Object named Co3DHexagon, supporting a single custom interface named IDraw. Add a single method to the IDraw interface (I bet you can guess the name) called Draw(). Implement Draw() however you desire:

// Draw method A. STDMETHODIMP CCo3DHexagon::Draw() {      MessageBox(NULL, "Drawing a 2D Hex", "CoHex says...",                MB_OK | MB_SETFOREGROUND);      return S_OK; } 

Now, edit your IDL file to add support for a new interface named I3DRender, which also supports a single method named Draw():

// Draw method B. [object, uuid(DD900360-2C02-11d3-B901-0020781238D4)] interface I3DRender : IUnknown {      [helpstring("Draw in 3D")] HRESULT Draw(); }; ... coclass Co3DHexagon {      [default] interface IDraw;      interface I3DRender; };

Next, support this interface in your ATL coclass with or without the aid of the Implement Interface Wizard. Either way, you will have a COM map that now looks like the following:

// Clients can get a pointer to IDraw and I3DRender, however // each Draw() method calls the same shared implementation. BEGIN_COM_MAP(CCo3DHexagon)      OM_INTERFACE_ENTRY(IDraw)      OM_INTERFACE_ENTRY(I3DRender) END_COM_MAP()

At this point, build a simple Visual Basic test client. You will be able to obtain each interface from the coclass; however, they each call the same shared implementation of Draw(). There is the name clash. What we really want is to have the Draw() method of each interface respond in a unique manner.

Step Two: Resolve the Name Clash

The first point to be aware of when resolving a method name clash with ATL is that you must create a wrapper for each interface involved in the name clash. You cannot simply trap one of the ambiguous interfaces and leave the other(s) alone. Insert a new file named wrappers.h. In this file, create a wrapper class around the I3DRender and IDraw interfaces using the same approach as outlined in this chapter: Intercept the offending method, and call a uniquely named method implemented by the coclass:

// This IMPL class wraps the offending // Draw method of the I3DRender interface. struct I3DRenderImpl : I3DRender {      STDMETHODIMP Draw()      {           return Draw3DHex();      }      // This method is implemented by the coclass.      STDMETHOD(Draw3DHex)() = 0; }; struct IDrawImpl: IDraw {      STDMETHODIMP Draw()      {           return Draw2DHex();      }      // This method is also implemented by the coclass.      STDMETHOD(Draw2DHex)() = 0; }; 

Now that we have created wrapper classes to resolve the name clash, we need to retrofit Co3DHexagon to use them. Edit your inheritance chain to derive only from the wrapper classes, and comment out (or remove) the original IDraw and I3DRender interfaces (your COM map will remain unaltered):

// Derive from your wrapper classes. class ATL_NO_VTABLE CCo3DHexagon :      public CComObjectRootEx<CComSingleThreadModel>,      public CComCoClass<CCo3DHexagon, &CLSID_Co3DHexagon>,      // public IDraw,      // public I3DRender,      public IDrawImpl,      public I3DRenderImpl { ... };

Recall that the wrapper classes each define a pure virtual function that needs to be implemented by the supporting coclass. In the class definition of Co3DHexagon, remove the existing Draw() prototype and implementation code, and add a prototype for Draw2DHex() and Draw3DHex():

// These were defined by the wrappers. STDMETHOD(Draw2DRect)(); STDMETHOD(Draw3DRect)();

Now, we will implement the wrappers to define a unique implementation for each Draw() method:

// Give a custom implementation for each wrapper. STDMETHODIMP CCo3DHexagon::Draw3DRect() {      MessageBox(NULL, " Stunning 3D!!", " CoHex says",                MB_OK | MB_SETFOREGROUND);      return S_OK; } STDMETHODIMP CCo3DHexagon::Draw2DRect() {      MessageBox(NULL, "Drawing a 2D Hex", "CoHex says",                MB_OK | MB_SETFOREGROUND);      return S_OK; }

Compile the project and run the VB test client again. You should now see a unique response for each Draw() method, depending on the interface!

Also recall that if an interface has additional methods defined which do not cause a name clash, nothing needs to be accounted for in the wrapper. For example, assume that IDraw has an additional (and unique) method:

// A non-clashing method. interface IDraw : IUnknown {      [helpstring("Draw in 2D")] HRESULT Draw();      HRESULT DrawBold(); }; 

Your wrapper class (IDrawImpl) would be unaffected, and would still simply be in charge of trapping the offending Draw() method. Co3DHexagon would provide an implementation of DrawBold() as usual.



 < 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