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:\MyProject\test.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:\AtlDbgHeapDemo\ATlDebugHeapDemoObj.cpp(36) : {44} normal block at
  0x01F50F70, 3 bytes long.
  Data: < > CD CD CD 
 H:\AtlDbgHeapDemo\ATlDebugHeapDemoObj.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:\atldbgheapdemo\atldebugheapdemoobj.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:\IE5\IEXPLORE.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



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • If you need new tools and tricks to make your meetings profitable and productive, then 5 Tips in 5 Minutes: A Quick Guide for More Profitable Sales Meetings is for you. Timely, practical tips that you can incorporate in just seconds will save you literally hours in travel and meeting time, not to mention help you to focus on what your sales prospects really want to know and how you can meet their needs. Get 5in5: A Quick Guide for More Profitable Sales Meetings and start building your sales the smarter, faster …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds