GDI memory leak in Windows Forms
25/02/2009
A while ago I run into a rather interesting and insidious bug, which I thought I would share.
The scenario
We had a desktop application developed on the .NET Framework 2.0 using Windows Forms. Part of the requirements for this application was that the standard Windows controls had to be modified to have a custom appearance, to suite the customer’s needs. Since classes that are part of Windows Forms are really nothing more than thin
managed wrappers around Windows’ graphic subsystem GDI, many customizations that required the use of functions not directly exposed through .NET, forced us to invoke the Win32 API directly.
Now, for someone who has been developing on a virtual machine for a long time (whether it be the CLR or Java), commodities like garbage collection are quickly taken for granted. And when the time comes to step out of the safe and cozy managed world, it is easy to forget that those assumptions are no longer valid. And that’s where problems usually start.
As a side note, if we were to build the same application today we would definitely choose Windows Presentation Foundation (WPF) over Windows Forms as UI technology, since WPF allows to easily define the controls’ visual layout separately from their functionality without leaving the CLR.
The bug
Back to our case. Our Windows Forms application would run fine under normal operations, for about 3 hours until it suddenly crashed reporting a System.OutOfMemoryException (OOM). We also noticed that the application would survive for a shorter period of time if it was used more “intensively” (meaning opening and closing a lot of forms).
By looking at the log files we could determine that the exception was always originated from the constructor of a Bitmap object, which lead us to think that we were looking at a memory leak of some sort of unmanaged graphic resources.
Like everything else in the System.Drawing and System.Windows.Forms namespaces, also the Bitmap class holds a reference to a corresponding GDI object, which is a resource allocated outside of the CLR and as such it must explicitly be released when no longer in use.
Oddly enough, a through examination of the source code confirmed that all bitmap objects were correctly released from memory by calling the Dispose method on them. So what was leaking?
To further investigate exactly what was being used and left hanging around, we used a free tool called GDIUsage, which shows exactly how many and which kinds of GDI objects are being allocated by an application. On the left picture you can see how memory looked like at application startup compared to after a couple of minutes of normal usage.
It was apparent that we were creating an awful lot of font objects and forgetting to delete them! But why would it take a long time for the application to crash? Were we hitting some kind of threshold?
It turned out Windows puts a limit on the number of total GDI objects that can be created inside of a process. And that limit is exactly of 10.000 GDI objects in Windows XP. This means that allocating GDI object number 10.001 will always cause an error. You can use Task Manager to see how many GDI resources each process has currently allocated by selecting the GDI Objects column.
The solution
At this point it was a matter of tracking down where in the source code we were creating System.Drawing.Font objects. Considering the rate of growth of these fonts instinct suggested it ought to be in some kind of global function that was being called from different places in the application. Here is what we found:
[DllImport("gdi32.dll")] private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); [DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr hObject); [DllImport("gdi32.dll", EntryPoint = "GetTextExtentPoint32A")] private static extern bool GetTextExtentPoint32( IntPtr hDC, string lpString, int cbString, ref SIZE lpSize); [StructLayout(LayoutKind.Sequential)] private struct SIZE { int cx; int cy; } /// <summary> /// Gets the width and height of a string with the specified font. /// </summary> /// <param name="g">The graphic context to measure the string.</param> /// <param name="text">The string to be measured.</param> /// <param name="font">The font used to display the string.</param> /// <returns>The size of the string in width and height.</returns> public static SizeF MeasureString(Graphics g, string text, Font font) { SIZE sz = new SIZE(); IntPtr hdc = g.GetHdc(); IntPtr prevFont = SelectObject(hdc, font.ToHfont()); GetTextExtentPoint32(hdc, text, text.Length, ref sz); DeleteObject(SelectObject(hdc, font.ToHfont())); g.ReleaseHdc(hdc); return new SizeF((float)sz.cx, (float)sz.cy); }
Sure enough the MeauseString method was being called from all over the application (don’t ask why). Do you notice anything particularly odd in the code? Let me show you the offending line:
// The Font.ToHFont() method creates a new Font GDI object // whose reference is being passed as argument // to the SelectObject Win32 function // but is never explicitly deleted IntPtr prevFont = SelectObject(hdc, font.ToHfont());
And here is how we fixed it:
public static SizeF MeasureString(Graphics g, string text, Font font) { IntPtr hdc = IntPtr.Zero; IntPtr f = IntPtr.Zero; IntPtr prevFont = IntPtr.Zero; SIZE sz; try { sz = new SIZE(); hdc = g.GetHdc(); f = font.ToHfont(); prevFont = SelectObject(hdc, f); GetTextExtentPoint32(hdc, text, text.Length, ref sz); } finally { DeleteObject(f); DeleteObject(prevFont); g.ReleaseHdc(hdc); } return new SizeF((float)sz.cx, (float)sz.cy); }
As you can see we made sure the DeleteObject Win32 function is called to release the Font object created by the Font.ToHFont() method.
Lessons learned
So, what did we learn from this experience? We can summarize it in 3 rules of thumb when dealing with unmanaged code in .NET, whether it be Win32, COM, you name it:
- Pay attention if any of unmanaged functions you are calling goes out and creates a new instance of some resource in memory. Carefully reading the specific API documentation is vital.
- Every time you allocate memory it is your responsibility to explicitly free it when it is no longer needed by the program. No Garbage Collector will do this for you, you are on your own.
- When creating a new unmanaged object, save a reference to it in a variable that you can use to reach that same object at a later time and remove it from memory. If you lose the reference before the object is explicitly destroyed, you have no way to reach that memory and it leaks.
I hope this rules will help you avoid some of the most common pitfalls leading to memory leaks in your own applications.
/Enrico



01/03/2009 at 12:25
Just passing by.Btw, your website have great content!
02/03/2009 at 09:28
Thanks, I appreciate it :-)
27/08/2009 at 17:33
It is really useful to know such unknown things where we usually made mistakes on disposing managed objects. I’m also searching my code path to determine what could be the root cause of GDI objects growth. Thanks for your detailed information.
27/08/2009 at 21:56
Thanks for your feedback.
GDI is indeed a difficult API to work with.
Hopefully as modern UI frameworks such as Windows Presentation Foundation (WPF) become mainstream, we won’t have to deal with GDI anymore.
16/12/2009 at 17:43
Thank you, it solved my problem!
16/12/2009 at 21:53
Glad I could help :-)
12/06/2011 at 18:32
Thanks for a superb article!
Enabled me to fix my leaking font crashes from GetGlyphOutline,
IntPtr prev = SelectObject(hdc, font.ToHfont()).
12/06/2011 at 19:13
@Richard Thanks, I appreciate it. I’m glad it could help you solve your problem :)
31/10/2011 at 15:15
Astonishing. I’ve been trying to find the memory leak in my program for about 2 days now, and I discovered (the hard way) that is was in Font.ToHfont(). Just can’t be, I told myself. Then I searched the web for confirmation and found this page.
I decided to put HFont (and HDC) in the constructor, and just use that, rather than calls to GetDC/ReleaseDC, and ToHfont/DeleteObject. Do you see any problem with this?
02/11/2011 at 00:17
@Dom Having a pointer to an HFont or any other GDI object as a class variable is a good way to reuse existing GDI resources throughout the lifetime of an object. However, the class should implement IDisposable to give the consumers a way to explicitly release those unmanaged resources, once they’re done using the object. The same cleanup should also be done in the class’ destructor (or Finalizer in .NET’s terminology) in case a consumer failed to invoke the Dispose method before the object had become unreachable. This is to make sure that the GDI resources being held finally get released before the object is destroyed by the Garbage Collector.
14/11/2011 at 15:40
From where I call this MeasureString because there are lot of windows forms in my application?
14/11/2011 at 20:04
@Naeem I’m not sure I understand your question. Could you provide some more information about your scenario?
15/11/2011 at 06:40
Thanks for your quick reply.
You wrote in your blog that “Sure enough the MeauseString method was being called from all over the application (don’t ask why)”.
I add these declaration and function in my Main class where Main() function located. My application contains lot of Win Forms and facing severe memory leaks and our client needs to restart the system because it hangs the system. Guide me from where I should call this function and make sure it is called from all over applications.
I run GDIUsage utility, the utility work first time properly. Whenever I start it next time it crash when click on take snap short button. I have to restart my system.
I test one of my popup form and disposing all controls in it and form itself but memory remains same and never release after form close and dispose its all child controls and form itself.
16/11/2011 at 09:28
@Naeem
In my case, the fact that the
MeasureStringmethod was being called many times only served to amplify the problem, eventually causing the application to crash. The real problem was that some GDI resources (more precisely HFont objects) were being allocated in the body of that method and were never released, effectively leaking them.If your Windows Forms application crashes because of a GDI memory leak and GDIUsage doesn’t help you identify what kind of GDI objects are being leaked, you’ll have to manually review the code to find all the places where GDI objects are being created and make sure you invoke the DeleteObject function on them when they’re no longer needed.
I hope this helps.
17/11/2011 at 07:17
GDI Usage utility gives me following objects which increase on each form popup and never reduce on form close and disposed.
Object————–Snapshot——————-New
—————————————————————-
Bitmaps————-14—————————195
Mem DC————17—————————265
and it keep increasing.
24/02/2012 at 20:48
Interesting article
15/03/2012 at 19:25
I usually use deleaker for debugging gdi and user objects)))