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
      } // 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 (3)

  1. jfo's coding says:

    Custom PaintingPainting best practices ComboBox OwnerDrawLayoutDock layout/Using the Splitter control…

  2. jfo's coding says:

    I got a note from Mukund, who is investigating a memory leak problem. 

    Hi Jessica, Is it true…

  3. jfo's coding says:

    A friend of mine ran into this the other day.

    If you call a method to get a handle some sort of System.Drawing…

Skip to main content