A class for helping track down GDI leaks
Finding leaks
This article describes the various methods for tracking down GDI and User handle leaks. Unfortunately in Win2k/XP you cannot tell which kind of handles are out (windows 9x used to distinguish) - however I haven't usually needed to know this kind of information.
If you open up task manager and add in the GDI/User handle column - the article discusses this - you should be able to observe when the handles spike in your application. Once you've narrowed down roughly where you are observing the spike, say hovering over a button keeps growing the number of GDI handles by 4 every time.... you can add in calls to GetGUIResources to divide and conquer the problem. Usually you'll find something IDisposable that wasnt disposed - like a pen or a brush or a string format object.
I thought I'd share the class I use in some of my tests to ensure that my painting code doesn't leak. I run the painting code through once to make sure all the static SystemPens/Brushes are all cached in, then rerun it wrapping the painting code in a GDICounter object.
private bool runonce = false;
protected override OnPaint(PaintEventArgs e) {
if (runonce) {
using (new GDICounter()) { // the constructor snaps the current count of GDI objects by calling GetGUIResources
base.OnPaint(e);
} // the dispose method is called here and compares the current count to the last known number.
}
runonce = true;
}
namespace FindLeak
{
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
public class GDICounter : IDisposable
{
private const int GR_GDIOBJECTS = 0, GR_USEROBJECTS = 1;
[DllImport("User32", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetGuiResources(IntPtr hProcess, int uiFlags);
private int intialHandleCount;
public GDICounter() {
intialHandleCount = 0;
intialHandleCount = GetGDIObjects();
}
private int GetGDIObjects() {
IntPtr processHandle = System.Diagnostics.Process.GetCurrentProcess().Handle;
if (processHandle != IntPtr.Zero) {
return (int)GetGuiResources(processHandle, GR_GDIOBJECTS);
}
return 0;
}
public void Dispose() {
int currentHandleCount = GetGDIObjects();
if (intialHandleCount != currentHandleCount) {
int change = currentHandleCount - intialHandleCount;
if (change > 0) {
Debug.Fail("Handle count changed by: " + change.ToString() + new StackTrace().ToString());
}
}
}
}
}
Once you've established there is a leak, you can divide and conquer by sprinkling in more calls to GetGUIResources until you've found your problem.
Why is a leak bad? Wont the garbage collector get it?
This is exactly what you dont want to have happen. System.Windows.Forms/System.Drawing keeps a close eye on the number of handles out, to make sure that carefree usage of Pens/Brushes/Controls etc is cleaned up before the operating system runs out of resources. Under the covers there are threshholds of handle counts that are appropriate for each handle type - if this is exceeded, a garbage collection is performed to clear out the controls/pens/brushes that have yet to be finalized. If the app is creating lots of pens and brushes and not disposing them, this collection could happen at a time that's not convenient - say in the midst of a paint.
This can all be prevented by deterministically cleaning up these resources (via the Dispose method). More details on how, when, where, and why you should use dispose here.
Comments
- Anonymous
August 31, 2005
Custom PaintingPainting best practices ComboBox OwnerDrawLayoutDock layout/Using the Splitter control... - Anonymous
May 10, 2006
I got a note from Mukund, who is investigating a memory leak problem. 
Hi Jessica, Is it true... - Anonymous
July 25, 2006
A friend of mine ran into this the other day.
If you call a method to get a handle some sort of System.Drawing...