Defining COM Interfaces Using Standard COM Macros

 < Free Open Study > 



When developing COM interfaces, you could decide to use straight C++ and build an interface using the class, interface, or struct keywords. However, these interface definitions will be far less robust than the same interface defined using the set of COM-specific interface macros. When we discuss the Interface Definition Language (IDL) in the next chapter, we will see how the Microsoft IDL compiler (MIDL) generates C/C++ interface code using COM macros automatically. Real COM developers always begin with IDL, but for the time being, we will make direct use of the COM macros directly.

If you were to examine the exact definition of IUnknown as seen in <unknwn.h>, what you would really see is the following:

// Standard COM interfaces are wrapped by numerous COM macros. MIDL_INTERFACE("00000000-0000-0000-C000-000000000046") IUnknown { public:      BEGIN_INTERFACE           virtual HRESULT STDMETHODCALLTYPE QueryInterface(           /* [in] */ REFIID riid,           /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;           virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;           virtual ULONG STDMETHODCALLTYPE Release(void) = 0;      END_INTERFACE };

The BEGIN_INTERFACE and END_INTERFACE expand to nothing on Win32 platforms, and to keep things simple, we will not be using these macros in our interface definitions. The MIDL_INTERFACE macro resolves to the struct keyword and (if compiled on VC 5.0 or greater) a declspec allowing an IID to be directly attached to the interface:

// The MIDL_INTERFACE macro, defined in <rpcndr.h> #if _MSC_VER >= 1100 #define MIDL_INTERFACE(x)  struct __declspec(uuid(x)) __declspec(novtable) #else #define MIDL_INTERFACE(x)  struct #endif

The justification to use these COM macros amounts to correct expansion of your interface on different platforms. Thus, the IDraw interface will be interpreted a bit differently on a Macintosh than it would on the various Windows platforms (based on calling conventions, pointer logic, and whatnot). The good news is you do not need to know the exact differences needed for a given platform; simply use the set of COM macros and they will expand correctly based on preprocessor logic.

There are two basic sets of COM interface macros. One set is used when defining the interface itself (for example: IDraw), another for the coclass implementing the interfaces (for example: CoHexagon). Each set has two variations, depending on the physical return value of the interface method, thus giving us a total of four basic macros defined in <objbase.h>: STDMETHOD, STDMETHOD_, STDMETHODIMP, and STDMETHODIMP_.

Defining Interfaces Using COM Standard Macros

When you are defining a COM interface (such as IDraw) in C++, use the STDMETHOD or STDMETHOD_ macros. If your interface method returns an HRESULT, use STDMETHOD. The only parameter to STDMETHOD is the name of the method. Here is the expansion of STDMETHOD on Win32:

// Use STDMETHOD when defining a method which returns an HRESULT #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method

If you return anything other than HRESULT (including void), use STDMETHOD_. Here you must pass two arguments to the macro, the return type and method name:

// STDMETHOD_ is used if your method returns anything other than an HRESULT. #define STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method

Notice that these two macros make use of yet another macro, STDMETHODCALLTYPE, which is defined in <winnt.h> simply as:

// Under Win32, STDMETHODCALLTYPE resolves to __stdcall #define STDMETHODCALLTYPE    __stdcall 

Let's rewrite IShapeEdit, this time using the correct COM interface macros:

// IShapeEdit using the COM macros. interface IShapeEdit : public IUnknown      {      STDMETHOD_(void, Fill) (FILLTYPE fType) = 0;           STDMETHOD_(void, Inverse) () = 0;      STDMETHOD_(void, Stretch) (int factor) = 0; };

To be even more COM compliant, we really should return the standard HRESULT instead of void. Let's rewrite IShapeEdit again using the COM return value of choice (note the substitution of STDMETHOD for STDMETHOD_ ):

// A even more COM savvy version of IShapeEdit. interface IShapeEdit : public IUnknown {      STDMETHOD(Fill) (FILLTYPE fType) = 0;           STDMETHOD(Inverse) () = 0;      STDMETHOD(Stretch) (int factor) = 0; };

When defining COM interfaces using the set of macros, we do have some other helper items defined in <objbase.h>:

// Additional, but not core, COM macros. #define PURE   = 0 #define DECLARE_INTERFACE(iface)  interface iface #define DECLARE_INTERFACE_(iface, baseiface)  interface iface : public baseiface

The PURE macro evaluates to the =0 suffix of a pure virtual function definition. The DECLARE_INTERFACE variations allow you to send in interface names and any base interface as parameters. These macros are not necessary, but can help streamline interface definitions. Here would be a final variation of IShapeEdit using these optional macros:

// A very COM compliant interface definition. DECLARE_INTERFACE_(IShapeEdit, IUnknown) {      STDMETHOD(Fill) (FILLTYPE fType) PURE;           STDMETHOD(Inverse) () PURE;      STDMETHOD(Stretch) (int factor) PURE; };

With the above interface definition, we can rest assured that the macros expand correctly on a given platform. This is becoming more and more important, as COM is being ported to many flavors of UNIX, IBM variations, and so forth. If you write COM interface definitions in C++, get in the habit of learning these macros and sticking to them. Better yet, use IDL and let the MIDL compiler add them for you automatically (as we will do beginning in Chapter 4).

Implementing Interfaces Using COM Standard Macros

When you are creating interfaces defined with the STDMETHOD and STDMETHOD_ macros, you will need to use a corresponding set in the implementing coclass: STDMETHODIMP and STDMETHODIMP_. The presence or absence of the underscore is also determined by the interface method return type (HRESULT or anything else). These two macros are defined as follows:

// The COM class implementing a COM interface makes use of these macros. #define STDMETHODIMP                HRESULT STDMETHODCALLTYPE #define STDMETHODIMP_(type)         type STDMETHODCALLTYPE

Each macro simply complements the calling conventions used in the interface definition, and removes the virtual keyword from the expansion. If our CoHexagon class implements the IShapeEdit and IDraw interfaces (both defined with the COM interface macros), we would write the following:

// As our custom interfaces used the STDMETHOD macros, our coclass // must make use of the corresponding STDMETHODIMP macros. class CoHexagon : public IShapeEdit, public IDraw { public:  ...      STDMETHODIMP QueryInterface(REFIID riid, void** pIFace);      STDMETHODIMP_(ULONG)AddRef();      STDMETHODIMP_(ULONG)Release();      STDMETHODIMP Draw();      STDMETHODIMP Fill(FILLTYPE fType);      STDMETHODIMP Inverse();      STDMETHODIMP Stretch(); private:      ULONG m_refCount;          // Initialize to zero in constructor. };

In CoHexagon's *.cpp file, we would implement methods using the same IMP macros as used in the coclass's header file. AddRef() would be implemented as the following:

// Need IMP_ as AddRef() does not return an HRESULT. STDMETHODIMP_(ULONG) CoHexagon::AddRef()      {      return ++m_refCount; }

QueryInterface() would be implemented with STDMETHODIMP, as QueryInterface() returns the standard HRESULT:

// Need IMP as QueryInterface() does return an HRESULT. STDMETHODIMP CoHexagon::QueryInterface(REFIID riid, void** ppv) {      if(riid == IID_IUnknown)           *ppv = (IUnknown*)(IDraw*)this;     // See below...      else if(riid == IID_IDraw)           *ppv = (IDraw*)this;      else if(riid == IID_IShapeEdit)           *ppv = (IShapeEdit*)this;      if(*ppv)      {           ((IUnknown*)(*ppv))->AddRef();           return S_OK;      }      return E_NOINTERFACE; } 

Notice the extra cast for the IUnknown provision. As both IDraw and IShapeEdit derive from IUnknown, we get an ambiguity error if we cast directly to IUnknown*. This extra cast keeps the compiler happy (we could have used any interface supported by the coclass as part of this cast, not just IDraw). All we are doing here is taking some IUnknown- derived interface and sucking out the "IUnknown-ness" from it.

Note 

There is some inconsistency regarding the correct use of the IMP macros in the COM literature. Sometimes you will find the IMP macro set used in both the method prototypes (*.h files) and implementation in the coclass (*.cpp) as we have seen here. Other times, you will see STDMETHOD used in both the interface definition and again in the coclass method prototypes, leaving the corresponding IMP macro in the *.cpp file only. Although the use of either STDMETHOD or STDMETHODIMP macros in the coclass header file will compile just fine, the justification of sticking to the IMP set in both the prototype and implementation is, when expanded, the virtual keyword is removed, and is technically more correct.



 < 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