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.
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.
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.
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.
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)
.
.
.
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
#define METHOD_PROLOGUE(theClass, localClass) \
theClass* pThis = \
((theClass*)((BYTE*)this - offsetof(theClass, m_x##localClass))); \
AFX_MANAGE_STATE(pThis->m_pModuleState) \
pThis;
which the preprocessor replaces with
CWidgets* pThis = \
((CWidgets*)((BYTE*)this - offsetof(CWidgets, m_xWidgets:))); \
AFX_MANAGE_STATE(pThis->m_pModuleState) \
pThis;
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.
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.
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.
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.
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.