Wednesday, October 21, 2009

ATL Removal: Part 1 – Removing BEGIN_COM_MAP() and END_COM_MAP()

There are some who feel that ATL is the best thing since sliced bread for COM programming. ATL can abstract a lot of the nitty gritty COM programming away from the COM programmer. My goal today is not to bash ATL. I think it can be a great time saver if used correctly for the right level of code. If you are programming UI, or an IE plugin, go right ahead and use ATL if you like. If you are writing lower level system APIs, please don’t. Also, ATL cannot be used for an excuse for not understanding COM programming or memory management. I have spent the last year fixing lots of bugs because someone didn’t understand what ATL was doing, things like: double frees, double releases, memory leaks, leaked references, and so on. In other words, ATL is not a sliver bullet for API usage ignorance. I don’t like using ATL because: writing straight COM code is not much harder, and ATL makes debugging more annoying. So, in the end for me, ATL does not buy me much, and is somewhat annoying. If you like ATL and know what it is really doing so you use it correctly, go ahead and use it. Independent of my no ATL preference, one API I am maintaining needs to be “low level” and therefore should not depend on ATL, so I am removing ATL from that API.

ATL wants to internally manage the ref counts to your ATL based com object. To do this, it wraps your com object using a template class called CComObject. CComObject basically adds two extra layers to your com object. For debugging, it does make things a little bit more annoying, but it is supposed to manage the lifetime of your object for you.

The first thing to do is to remove the COM_MAP macros needed to set up all of the CComObject hooks. When you have an ATL generated COM object, it creates for you something like this in your header:

BEGIN_COM_MAP(CYourInterface)
COM_INTERFACE_ENTRY(IYourInterface)
END_COM_MAP()

Basically these macros help write your IUnknown implementation. Not really that big of a time saver as it is not that hard to implement IUnknown. The first step is to remove COM_MAP macro stuff from your header and swap it for the IUnknown definition. Here is one example that you see often:

STDMETHOD(QueryInterface)(IN REFIID riid, OUT void ** ppv);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);

Next, in your cpp file where you implement your com object, you will need to add the implementation of IUnknown. This too is almost boiler plate.

//////////////////////////////////////////////////////////////////////
// Implementation : IUnknown
//////////////////////////////////////////////////////////////////////

ULONG CYourInterface::AddRef()
{
return InterlockedIncrement(&m_cRef);
}

ULONG CYourInterface::Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);

if (0 == cRef)
{
delete this;
}

return cRef;
}

HRESULT CYourInterface::QueryInterface(REFIID riid, __deref_out_opt void **ppv)
{
HRESULT hr = S_OK;

if (ppv)
{
*ppv = NULL;
}
else
{
hr = E_INVALIDARG;
}

if (S_OK == hr)
{
if ((__uuidof(IUnknown) == riid) (riid == __uuidof(IYourInterface)))
{
AddRef();
*ppv = (IYourInterface *)this;
}
else
{
hr = E_NOINTERFACE;
}
}

return hr;
}

Finally make sure that you add a:

LONG m_cRef;

Member in your class, and make sure you initialize it to 1 in your constructor.

m_cRef(1)

No comments:

Post a Comment