Click here to Skip to main content
15,868,043 members
Articles / Desktop Programming / MFC
Article

An introduction to MFC's COM Interface Macros

Rate me:
Please Sign up or sign in to vote.
4.49/5 (16 votes)
15 Apr 2004CPOL6 min read 79.6K   41   5
How to define and implement COM interfaces inside an MFC class

Introduction

As part of a project I'm working on I need to implement multiple COM interfaces.

If I wanted to implement the IWidget interface and the IThingAMaBob interface it might look like this.

C++
class CWidgets : public IWidgets
{
public:
    virtual HRESULT __stdcall QueryInterface(REFIID riid, 
                                     void __RPC_FAR *__RPC_FAR *ppvObject);
    virtual ULONG   __stdcall AddRef(void);
    virtual ULONG   __stdcall Release(void);

    virtual HRESULT __stdcall MyExtraFunction();
};
and similarly for the IThingAMaBob interface. This is COM 101 material.

It becomes a bit tedious coding this all the time. Factor in the need to implement, for each interface, the IUnknown base methods correctly (especially QueryInterface) and the smart programmer starts researching better (easier) ways to do it.

Fortunately, for the MFC programmer, MFC provides a much easier way to not only implement a COM interface, it provides an easy way to define multple interfaces in the one class.

MFC COM Macros

We can rewrite our CWidgets class like this, using some MFC macros.
C++
class CWidgets : public CCmdTarget
{
public:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(Widgets, IWidgets)
        STDMETHOD(MyExtraFunction)();
    END_INTERFACE_PART(Widgets)
};
The first thing you'll notice is that the class is now derived from CCmdTarget. It needs to be derived from CCmdTarget either directly or indirectly for reasons we'll see a little later. The class then declares an interface map followed by an interface part. The interface part defines a nested class called (in this case) XWidgets which is derived from the IWidgets interface. (The macro prepends an X to the first parameter of the macro to give the nested class a unique name that won't clash with the 'C' or 'I' names). The BEGIN_INTERFACE_PART macro also declares the 3 standard methods inherited from IUnknown for us. Any additional methods for this interface must be defined between the BEGIN_INTERFACE_PART and END_INTERFACE_PART macros.

In our implementation file we'd have something like this.

C++
BEGIN_INTERFACE_MAP(CWidgets, CCmdTarget)
    INTERFACE_PART(CWidgets, IID_IWidgets, Widgets)
END_INTERFACE_MAP()
plus our implementation of the methods of the IWidgets interface. Note that even though we don't have to declare the basic IUnknown methods we do have to implement them. The four methods of our IWidgets interface might look like this.
C++
STDMETHODIMP_(ULONG) CWidgets::XWidgets::AddRef()
{
    METHOD_PROLOGUE(CWidgets, Widgets);
    
    return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CWidgets::XWidgets::Release()
{
    METHOD_PROLOGUE(CWidgets, Widgets)

    return pThis->ExternalRelease();
}

STDMETHODIMP CWidgets::XWidgets::QueryInterface(REFIID iid, LPVOID far* ppvObj)     
{
    METHOD_PROLOGUE(CWidgets, Widgets)

    return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CWidgets::XWidgets::MyExtraFunction()
{
    METHOD_PROLOGUE(CWidgets, Widgets)

    //  Do something
    .
    .
    .
    
    return S_OK;
}
Before we dive into this let's recap. Our class is derived from CCmdTarget and, via the BEGIN_INTERFACE_PART macro, contains a nested class called XWidgets.

The first thing you'll notice in the nested class implementation is a new macro, METHOD_PROLOGUE. It takes two arguments. The first is the name of the enclosing class, the second is the name of the inner class (without the X). If we look at the definition of the macro we see this

C++
#define METHOD_PROLOGUE(theClass, localClass) \
    theClass* pThis = \
        ((theClass*)((BYTE*)this - offsetof(theClass, m_x##localClass))); \
    AFX_MANAGE_STATE(pThis->m_pModuleState) \
    pThis; // avoid warning from compiler \
which the preprocessor replaces with
C++
CWidgets* pThis = \
    ((CWidgets*)((BYTE*)this - offsetof(CWidgets, m_xWidgets:))); \
AFX_MANAGE_STATE(pThis->m_pModuleState) \
pThis; // avoid warning from compiler \
which declares a pointer called pThis of type CWidgets and initialises it to point at the enclosing CWidgets class. It does this via some pointer arithmetic using the this pointer and m_xWidgets. Ignore, for now, how m_xWidgets got there, simply accept that it's an embedded instance of the XWidgets class.

Once we've got a pointer to the enclosing class we can access data in the enclosing class or call member functions on the enclosing class. This is where the derivation from CCmdTarget comes into play.

MFC provides us with an implementation of AddRef() and Release() which we can access via pThis. Hence the calls to ExternalAddRef() etc. ExternalAddRef() either calls our external IUnknown if our interface is aggregrated, or it increments a reference counter in our base CCmdTarget object. Since I'm not concerned in this article with aggregration that's the last mention you'll see of the subject. Similarly, calling ExternalRelease() decrements our reference count.

More importantly, MFC gives us a correct implementation of QueryInterface(). By correct I mean that it follows the semantics of QueryInterface. If you have an IWidgets interface you can use it to obtain an IUnknown interface. Or vice versa. Which is, of course, the way it's supposed to work but a surprisingly high number of programmers (myself included) sometimes forget to implement the query for IUnknown.

Instances of nested classes

Once we've defined our interface and written the implementation we need to have an instance of the class to use. One might be tempted to do this in our outer class.
C++
class CWidgets : public CCmdTarget
{
public:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(Widgets, IWidgets)
        STDMETHOD(MyExtraFunction)();
    END_INTERFACE_PART(Widgets)
    
    XWidgets    m_myWidget;
};
which defines the XWidgets class and then creates an instance of it embedded in the CWidgets object. That'll certainly compile but when your client code calls QueryInterface() to get an instance of the XWidgets class it won't get a pointer to m_myWidget. What it gets is a pointer to an instance of XWidgets called m_xWidgets. Hang on! Where did m_xWidgets come from?

Hidden inside the END_INTERFACE_PART macro is a declaration of an instance of the nested class. Note this well. Usually when you define a nested class you have to declare an instance of the nested class within the enclosing class. The MFC macros assume that you'll always have an embedded instance of the nested class in the enclosing object, so it creates one for you, naming the instance by prepending m_x to the nested class name.

Steak knives anyone?

So far so good. But wait, there's more! The macros also make it very easy to implement multiple interfaces in the one object. Let's expand our class a little to incorporate an implementation of the IThingAMaBob interface. Of course, it only makes sense to combine the two interfaces in the one object if a Widget and a ThingAMaBob are related so let's assume they are. Our class would now look like this.

C++
class CWidgets : public CCmdTarget
{
public:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(Widgets, IWidgets)
        STDMETHOD(MyExtraFunction)();
    END_INTERFACE_PART(Widgets)
    
    BEGIN_INTERFACE_PART(ThingAMaBob, IThingAMaBob)
        STDMETHOD(SomeOtherFunction)();
    END_INTERFACE_PART(ThingAMaBob)
};
which defines a second nested class called XThingAMaBob. In our implementation file the interface map now looks like this.
C++
BEGIN_INTERFACE_MAP(CWidgets, CCmdTarget)
    INTERFACE_PART(CWidgets, IID_IWidgets, Widgets)
    INTERFACE_PART(CWidgets, IID_IThingAMaBob, ThingAMaBob)
END_INTERFACE_MAP()
and we'd add whatever methods are part of the XThingAMaBob class (not forgetting the IUnknown methods). As part of all this we get a new member variable in the containing class called m_xThingAMaBob of type XThingAMaBob.

The really nice thing about this is that if QueryInterface() in your implementation calls pThis->ExternalQueryInterface() you'll automatically find and return the embedded m_xSomething instance associated with the iid you (or some other program) requested, with all the plumbing taken care of by the interface map and CCmdTarget. You don't have to write a bunch of code in each objects QueryInterface() method to make it aware of the other interfaces implemented on the containing object. More importantly, if you add an interface to the class, all you have to remember is to add it to the interface map in the implementation file and MFC will take care of the rest.

Reference Counting

Because the interfaces are defined within an MFC object they have the same lifetime as the enclosing object. Thus, as you've already seen, it's possible to do reference counting in the enclosing object rather than make each interface responsible for it's own counting. All interfaces implemented on the class share a single reference counter. In debug builds MFC does an ASSERT(dwRef <= 1) on the reference counter during the CCmdTarget::~CCmdTarget() destructor. If you encounter that assert it means that someone somewhere hasn't done a Release() on one or more of your interface pointers.

Variables, methods and constructors in the nested class

As well as defining COM methods for a nested class it's possible to embed normal (non COM) methods, constructors and destructors and member variables inside a nested class. All you have to do is add them between the BEGIN_INTERFACE_PART and END_INTERFACE_PART macros. The only 'special' thing you have to remember is that the constructor / destructor names are the classname with an 'X' prepended.
C++
class CWidgets : public CCmdTarget
{
public:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(Widgets, IWidgets)
        STDMETHOD(MyExtraFunction)();
    END_INTERFACE_PART(Widgets)
    
    BEGIN_INTERFACE_PART(ThingAMaBob, IThingAMaBob)
        STDMETHOD(SomeOtherFunction)();
        
        XThingAMaBob();
        ~XThingAMaBob();
        
    private:
        BOOL    m_bMyBool;
    END_INTERFACE_PART(ThingAMaBob)
};
Which defines a constructor / destructor pair called XThingAMaBob and a private BOOL variable called m_bMyBool. Within your nested class code you access members defined this way just as you would in a non nested class, via the implicit this pointer.

History

16 April 2004 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
QuestionUndefined? Pin
rjklindsay5-Jun-06 3:16
rjklindsay5-Jun-06 3:16 
There seems to be something important missing.
I keep getting the following error:

c:\Src\Rene\MapX\MapX\Widgets.h(23): error C2504: 'IWidgets' : base class undefined

On the line:

BEGIN_INTERFACE_PART(Widgets, IWidgets)

IWidgets IS defined in the IDL file, and does show up in the Class view. What am I doing wrong?

rjklindsay
GeneralScary Pin
Jörgen Sigvardsson17-Apr-04 4:26
Jörgen Sigvardsson17-Apr-04 4:26 
GeneralRe: Scary Pin
Rob Manderson17-Apr-04 10:29
protectorRob Manderson17-Apr-04 10:29 
GeneralSorry... Pin
Maximilian Hänel17-Apr-04 1:05
Maximilian Hänel17-Apr-04 1:05 
GeneralRe: Sorry... Pin
Rob Manderson17-Apr-04 10:28
protectorRob Manderson17-Apr-04 10:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.