Tracing memory leaks with ATL

.

Do you remember memory-leak tracemessages from MFC ? The debug heap of Visual C in
conjunction with Microsoft’s Foundation class library (MFC) dumps the memory of all
objects that have not been freed at the time when the program exits to the trace window of
Visual Studio.

Two years ago I switched over to ATL programming. In most cases I do not mix ATL and
MFC libraries within the same project. Suddenly I didn’t get the familiar


Dumping objects ->
H:MyProjecttest.cpp(36) : {44} normal block at 0x01F50F70, 3 bytes long.
Data: < > CD CD CD

message any more which allowed me to double-click and jump directly to the code where
the object had been created. Since then I have tried various debugging aids provided by
ATL but was never quite happy. A year ago I decided to extend ATL to bring back this
missing feature.  This article is the result of some cool template hacking.

Microsoft’s debug heap implementation allows programmers to associate a line number,
filename and some other information with each memory allocation. If it would be possible
to overload all memory allocation functions with the respective debug-heap allocation
functions and if I could associate filename and linenumber with each  allocation I
would get what I wanted. A peak at the MFC source code showed how to do this for new() and
malloc() by adding the following to each *.CPP file:


#ifdef _DEBUG
#define new DEBUG_NEW
#define malloc DEBUG_MALLOC
static char THIS_FILE[] = __FILE__;
#endif

DEBUG_NEW gets expanded to


#define DEBUG_NEW new(_NORMAL_BLOCK, THIS_FILE, __LINE__)

Unfortunately you can’t have a static variable in a *.H file, so this didn’t work for
ATL- and user-defined templates. Besides that I didn’t want to change any ATL files
provided by Microsoft.

ATL implements a class factory for each COM object. The class factory is responsible
for creating an instance of the associated object via a call to new()
With the goal to extend or replace ATL’s class factory code I started looking through the
ATL code for hooks and macros that I could overload.  Fortunately, the designers of
ATL defined the macros DECLARE_AGGREGATABLE and  DECLARE_NOT_AGGREGATABLE to define
which kind of classfactory should be used for a given object. Through several intermediate
steps these macros make an instance of  "CComCreator", which creates an
instances of the user-defined COM object via a call to new().

Without modifying MS’s code it was impossible to overload new() and I decided to create
my own CComCreator class:


template <class T1, class BASE> class CComCreator_DBG
{
public:

static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
ATLASSERT(*ppv == NULL);
HRESULT hRes = E_OUTOFMEMORY;
T1* p = NULL;

ATLTRY(p = new(_NORMAL_BLOCK,
"test.c", /* linenumber */ 1) T1(pv))
[….]

return hRes;
}
};

Now the challenge was to get the exact filename and line number where the object was
defined. Using the macros ‘__FILE__’ and ‘__LINE__’ didn’t do much good because they were
always pointing to the file that implemented the new CComCreator class.

After many experiments I settled on the following solution: If the user wants to use
the debug heap functions for the COM object he/she has to include
DECLARE_xxx_AGGREGATABLE_DBG(CMyClass)  inside the class definition of the object:

The macro gets expanded to


#define DECLARE_AGGREGATABLE_DBG(x) DECLARE_AGGREGATABLE_DBG2(x, __FILE__, __LINE__)

which gets expanded to


#define DECLARE_AGGREGATABLE_DBG2(x, _aStr_, _LineNo_)
public:
static inline const char* _GetFileName() { return _aStr_; }
static inline int _GetLineNo() { return _LineNo_; }
typedef CComCreator2< CComCreator_DBG< CComObject< x >, x
>, CComCreator_DBG< CComAggObject< x >, x > > _CreatorClass;

As you can see I am adding two static functions to the class that return the name and
location of the file where ‘DECLARE_xxx_AGGREGATABLE_DBG’ is
defined (in other words the *.H file). The debug version of CComCreator in return accesses
these functions via a call to ‘BASE::_GetFileName();’ and ‘BASE::_GetLineNo()’.


template <class T1, class BASE> class CComCreator_DBG

{
public:

static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
ATLASSERT(*ppv == NULL);
HRESULT hRes = E_OUTOFMEMORY;
T1* p = NULL;

//——————————————————————————————
// the following three lines are the only lines
that are different from MS’s implementation
const char* FileName = BASE::_GetFileName();
int LineNo = BASE::_GetLineNo();

ATLTRY(p = new(_NORMAL_BLOCK, FileName, LineNo)
T1(pv));
[….]

return hRes;
}
};

The source code contains some more macros but they work very similar. I have been using
the ATL debug heap for quite a while and I have always been very happy to discover memory
leaks right away. I hope it will also be useful to other ATL programmers as well.

 

How to use it:

First thing to do is to add  "DebugHeap.cpp"  to your project and
include "AtlDebugHeap.h" in your header file list.  As the last statement
in DllMain() add the following call


DebugHeap_DllMain(hInstance, dwReason, lpReserved);

which will initialize the debug heap at startup time and dump the leaks at the end. If
you want to trace memory leaks created via calls to new() and malloc() add the following
to each *.cpp file:


#ifdef _DEBUG
#define new DEBUG_NEW
#define malloc DEBUG_MALLOC
static char THIS_FILE[] = __FILE__;
#endif

Add  DECLARE_AGGREGATABLE_DBG(CYourClass) or
  DECLARE_NOT_AGGREGATABLE_DBG(CYourClass) to each COM object and you are done.

 

Example:

I have created a small sample project. The project creates an OCX (ProgID: AtlDbgHeapDemo.ATlDebugHeapDemoObj.1)
with a memory leak in 


CAtlDebugHeapDemoObj::FinalConstruct( )>

Search for    "@@@$$$@@@" to find
all the changes I have made. In order to test it you have to make an instance of the OCX
with a program running under the debugger (otherwise you won’t get the trace
messages). I tried to use MS’s testcontainer but it does not shut down if the ref-count of
any of the objects is not zero. Instead I have successfully tested it with VB and IE5.

In order to use IE5, compile the code, select IE under Program/Settings/Executable.
Once IE comes up, load the *.HTML file from the source directory. Shut down IE and you
should something similar to the following in the trace window of the debugger.


Dumping objects ->
H:AtlDbgHeapDemoATlDebugHeapDemoObj.cpp(36) : {44} normal block at
0x01F50F70, 3 bytes long.
Data: < > CD CD CD
H:AtlDbgHeapDemoATlDebugHeapDemoObj.cpp(33) : {43} normal block at
0x01F50F30, 20 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
h:atldbgheapdemoatldebugheapdemoobj.h(46) : {42} normal block at
0x01F50E70, 156 bytes long.
Data: < r t > BC 72 04 10 18 74 04 10 00 00 00 00 00 00 00 00
Object dump complete.
The thread 0xCC has exited with code 0 (0x0).
The program ‘G:IE5IEXPLORE.EXE’ has exited with code 0 (0x0).

As you can see the program contains three leaks . The last leak points
to the line where I have added the ‘DECLARE_AGGREGATABLE_DBG(CAtlDebugHeapDemoObj)’
statement. The debugger shows the filename and line number of each allocation and if you
click on it, the appropriate file gets opened and the cursor jumps to the given line
number.

Download source – 19 KB

Date Last Updated: May 17 1999

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read