Aggregating an ATL dual interface object to simplify writing dual interface controls in MFC

Ok, so what’s wrong with MFC’s ActiveX controls you ask..

Well, they are mostly ok, other than a few disadvantages that are sometimes
unfelt and sometimes can a poor programmer quite a headache..

The main problems with MFC’s implementation of ActiveX controls (if you
ignore the size of the MFC libraries) as I see it :

1. It’s hard to keep the code binary compatible.

When one uses the Class Wizard to add properties or remove
methodsproperties, Class Wizard renumbers the Dispatch IDs of the methods –
consequently, compiled code using the controlcomponent will not be able to
run it correctly.

Solution: Don’t use ClassWizard and do everything by hand.

Disadvantage: It’s quite a drag since you will have to change code in
several places for each method or property you add and you may not always
know what to change.

2. No dual support (ease of use)

ClassWizard produces a dispinterface supportting control and not a dual
interface supporting one (if you dont know the difference – there are
excellent articles in MSDN). The first drawback of this is that it’s hard,
messy and not too friendly to make calls to the control from C++.. Wrappers
have to be recreated when a control is updated etc.

Solution : For the ease of use in VC++ – Use #import on the control.

3. No dual support (speed)

Also, if you ever took a look at how MFC (or any other language which makes
life easier, for that matter) implements IDispatch, your heart has most
probably blackened from the amount of work done there for a simple call. All
this is removed when using a dual interface since the call is a direct one.

By the way – for some reason, VB still does not use dual interfaces when
calling controls. There is an MSDN article about VB4 not using dual
interfaces for controls – but it says nothing about VB6 or 5 (Q151903).

Solution: Create a second custom interface which copies the functionality of
the dispinterface.

Disadvantage: Each new method requires alot of places to be updated, plus
custom interfaces in MFC are not too friendly.

A solution for all this would be to use an ATL control instead of an MFC
one.. However, this does raise the amount of work needed, and raises it
considerably when your control does alot of graphic work or other work for
which MFC provides ample support and ATL provides bubkis.

Another solution, is to go read TN065 in MSDN (Dual-Interface support for
OLE Automation Servers) and follow these directions, which are, for the
reasons stated above, alot of hard work.

The solution which I came up with is to use an Aggregated ATL object to
replace the impl. of IDispatch. That way it’s:

1. Easy to keep the code compatible – no evil renumberring of IDs..

2. Easy to have dual support – ATL does that automatically.

Why did I go to the trouble of writing this article?

Well, the reason is that simple aggregation will not work – CCmdTarget
supports IDispatch and so will return that interface instead of the ATL
IDispatch. More on that in step 7.

Create an MFC ActiveX project via the wizard. I called mine AtlAggDisp and
the control’s class is named CAtlAggDispCtrl- it’s included here and was
created using VC6.0 Sp1.

(These steps were to create a sample project. The following numbered steps
can be done to any project)

1. Ask DevStudio to add an ATL object – if you haven’t done so in your
project yet, it will ask you if ATL support should be added – say Aye!

2. Add a Simple Object with the ATL wizard and give it a name – AggDisp
(class name CAggDisp) in my sample.

3. Change the ODL – Your control needs to expose the fact that it supports
the dual interface – to do that, go to the ODL and look for the line with
the coclass in it, defining your class (in the sample ODL – coclass
AtlAggDisp). Change the [default] interface to be the interface supported by
the ATL created object – In my case – IAggDisp. The ODL would look something
like this:


[ uuid(380AB0D9-FBC9-11D2-A0FD-00A0C9D6EB6B),
helpstring(“AtlAggDisp Control”), control ]
coclass AtlAggDisp
{
// [default] interface _DAtlAggDisp; // This line is removed – this is the
MFC generated one
[default] interface IAggDisp; // This line is inserted instead – the
IAggDisp is the interface supported by the newly added ATL object.
[default, source] dispinterface _DAtlAggDispEvents;
};

4. Add the OnCreateAggregates method to your control – this will run when
MFC deems it appropriate to create aggregated objects. It’s a virtual method
and in the code, it will look like this:


BOOL CAtlAggDispCtrl::OnCreateAggregates()
{

}

5. Add members to both the ATL object and the control to hold pointers to
each others.


#include “AggDisp.h”
class CAtlAggDispCtrl : public COleControl
{
DECLARE_DYNCREATE(CAtlAggDispCtrl)

// Constructor
public:
CAtlAggDispCtrl();
CComAggObject<CAggDisp>* m_pAggDisp;
// Overrides
.
.
.
.

And in the ATL object:


class CAtlAggDispCtrl; // Forward declaration
class ATL_NO_VTABLE CAggDisp :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAggDisp, &CLSID_AggDisp>,
public IDispatchImpl<IAggDisp, &IID_IAggDisp, &LIBID_ATLAGGDISPLib>
{
public:
CAggDisp()
{
m_pCtrl=NULL;
}
CAtlAggDispCtrl* m_pCtrl;

6. Create the ATL object in the method and set the pointers- I did this via
the CreateInstance() ATL method, so that I can gain initimate access to the
object instance:


BOOL CAtlAggDispCtrl::OnCreateAggregates()
{
HRESULT hRes =
CComAggObject<CAggDisp>::CreateInstance((LPUNKNOWN)GetInterface(&IID_IUnknow
n),&m_pAggDisp);
m_pAggDisp->m_dwRef=1;
m_pAggDisp->m_contained.m_pCtrl=this;

return TRUE;

}

7. The aggregation part – this is the trickier part – since the control
supports IDispatch, and since that support is internal and pretty much
inseperatable from CCmdTarget (when it supports COM), we cannot use orthodox
aggregation – we must play a game with MFC – that game’s name is
GetInterfaceHook().

All the QueryInterface() methods inside MFC are not virtual (InternalQI,
ExternalQI etc..), however, they all call GetInterfaceHook() which enables
us to play with what happens inside there..

My implementation of that method looks something like this (of course u have
to add it to the .h file as well)..


LPUNKNOWN CAtlAggDispCtrl::GetInterfaceHook(const void* piid)
{
if((IID_IAggDisp==*(IID*)piid) || (IID_IDispatch==*(IID*)piid))
{
return (IAggDisp*)(&m_pAggDisp->m_contained);
}

return COleControl::GetInterfaceHook(piid);
}

In this piece of code, we check if the requested interface is either
IDispatch or the dual interface. Since the dual interface inherits from
IDispatch, we can return the same vtable for both requests..

That’s it for the initialization part.. All that one needs to do now is add
the methods to the ATL object and make calls to the m_pCtrl with the
appropriate parameters.

For example, if I wanted the control to give a messagebox with a string, I
would add a method to the ATL object through the ClassView named JustDoIt:


#include “AtlAggDispCtl.h”
////////////////////////////////////////////////////////////////////////////
/
// CAggDisp

STDMETHODIMP CAggDisp::JustDoIt(BSTR Msg)
{
CString st(Msg);
m_pCtrl->MessageBox(st,”Msg”);
return S_OK;
}

Download source – 17 KB

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read