When you're losing resources along the way, it's wise to invest in a few bread crumbs.
Introduction
The Windows NT 4.0 Task Manager provides quite a bit of useful information to developers. Of particular interest to me are the Handles and Threads columns, which indicate how many handles each process has open and how many threads each process is running. After installing Windows NT 4.0, I started a test program on a multithreaded server I had developed under NT 3.51. After several days of having simulated clients bang on the server, the Task Manager indicated the same number of threads running as expected, but quite a few more open handles than what the server had started with. Seeking an automated way to track down my resource leaks, I developed the method presented in this article. Although the example I present here is for tracking down Win32 system resource leaks using Visual C++ and MFC, the method is general enough to be adapted to other operating systems, other development environments, and user-defined resources.
Basic Explanation of the Method
The method I use to track resource leaks is a lot like the instrumented malloc commonly used to detect memory leaks. I redefine a Win32 function that allocates system resources with a macro. A debug build expands the macro, which invokes a special debug version of the function. This debug function, in addition to its normal duties of allocating resources, records the type of resource created and the file and line number where the resource was created. A collection class object maintains a collection of the handles created by such debug functions, as well as the debug information associated with each handle. The collection class for this method is called HandleTracker. When a resource is no longer needed, my debug version of the Win32 API CloseHandle removes the handle from the HandleTracker object. When the application ends, the HandleTracker object displays a list of system resources that were not returned to the system via CloseHandle, or displays a message indicating that no resources were leaked. These displays are directed to the debugger's output window.
Redefining Win32 APIs with Macros
The example presented in this article shows how to track mutex objects in Win32. A mutex is one of several synchronization objects available in Win32 for synchronizing threads in a multithreaded application. For the purposes of this discussion, a mutex is created using the Win32 API CreateMutex and is freed using the CloseHandle API.
Listing 1, htracker.h, shows how CreateMutex is redefined during debug builds as DEBUG_CREATE_MUTEX. CloseHandle is also redefined as DEBUG_CLOSE_HANDLE. Note that CreateMutex is first undefined. This prevents compiler warnings about macro redefinition since CreateMutex is itself a macro. (In winbase.h, Microsoft defines CreateMutex as CreateMutexA for ASCII projects and CreateMutexW for UNICODE projects.) The need to undefine first is typical for APIs capable of creating named objects in Win32. CloseHandle requires no such undefinition. CreateMutex's arguments are passed on to DEBUG_CREATE_MUTEX, as well as the file name and line number, using the predefined preprocessor symbols __FILE__ and __LINE__. Function prototypes are also provided for DEBUG_CREATE_MUTEX and DEBUG_CLOSE_HANDLE.
Using the HandleTracker Class
Listing 2, htracker.cpp, shows the implementation for class HandleTracker, and for class HandleStringPair, a simple class used by HandleTracker to pair a handle with its debug information. HandleTracker has two data members. myHandleStringPairs is a CPtrArray, an MFC class which is a dynamically resizable array of pointers to void. myMutex is a HANDLE to a mutex object. This mutex controls access to myHandleStringPairs when handles are added and removed via HandleTracker::Add and HandleTracker::Remove respectively. Using a mutex here ensures that multiple threads will not be accessing myHandleStringPairs at the same time.
Note that htracker.cpp does not #include htracker.h. If it did, the CloseHandle statement within DEBUG_CLOSE_HANDLE would also be redefined as DEBUG_CLOSE_HANDLE, and a recursive call would be entered from which there would be no return. A similar situation would develop for CreateMutex and all other such macros in htracker.h. Also note that HandleTracker does not track its own mutex object, myMutex. This is a result of not including htracker.h in htracker.cpp.
TheHandleTracker, a single instance of HandleTracker, is declared in htracker.cpp. Declaring the instance here ensures that TheHandleTracker is created before the program starts its main or WinMain function and that TheHandleTracker is destroyed only after main or WinMain exits.
When CreateMutex is called in a debug build, DEBUG_CREATE_MUTEX is called in its place. If a mutex object is successfully created, DEBUG_CREATE_MUTEX adds its handle, along with the file name, line number, and Win32 API name (CreateMutex), to TheHandleTracker via HandleTracker::Add. This function creates a string with the appropriate debug information, and allocates a new HandleStringPair object. A pointer to this HandleStringPair object is added to myHandleStringPairs. Calls to WaitForSingleObject and ReleaseMutex prevent threads from colliding with each other as they make additions to TheHandleTracker.
Similarly, DEBUG_CLOSE_HANDLE is called in place of CloseHandle in debug builds. DEBUG_CLOSE_HANDLE searches myHandleStringPairs for the handle to be closed, removes the corresponding HandleStringPair object from myHandleStringPairs, and deletes the object. DEBUG_CLOSE_HANDLE then returns the results of CloseHandle to the caller. If DEBUG_CLOSE_HANDLE could not find the handle in myHandleStringPairs it displays an error message. This can happen in several situations. For example, another Win32 synchronization object called a semaphore can be created using the CreateSemaphore API. Like handles to mutexes, handles to semaphores are also closed via CloseHandle. If CreateSemaphore is not redefined in a fashion similar to the redefinition of CreateMutex, the handle to a semaphore will never be added to TheHandleTracker, but it will still be closed via DEBUG_CLOSE_HANDLE. This situation could also indicate an attempt to close a handle twice or to close an invalid handle.
An Example
Listing 3, main.cpp, shows how this method can be used with very little effort once the appropriate macros and debug functions have been developed. Source files that track resources must #include the htracker.h file. Projects that track resources must include htracker.cpp as a project file.
The code in main.cpp creates two mutex objects, but calls CloseHandle on the handle to only one of them. The debugger's output window shows that the mutex created by CreateMutex on line 16 of main.cpp was a leaked resource.
Summary
Only mutex objects were considered here, but I have used this method with mutexes, event objects, named pipes, thread handles, file handles, and handles created with the DuplicateHandle API. This method does not track resources that may be allocated by static objects, which may be created and destroyed before or after TheHandleTracker. Extending this method to cover the entire Win32 API would be a monumental task, but I've found that most of my programs only use a few of the APIs that create Win32 objects.
Mike Monagle is a Senior Consultant at Berger & Co. Information Management Consultants (www.berger.com) in Denver, CO., specializing in C++ programming. He has a Master of Computer Information Systems from the University of Denver and a Bachelor of Science in Civil Engineering from California State Polytechnic University, Pomona. He is particularly interested in creating Win32 classes not provided by Microsoft's MFC or Borland's OWL, such as serial port, event log, service application, and multi-threaded named pipes server application classes. He can be reached at monagle@compuserve.com.