Share via


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...