I have a process that continually runs something in a loop. It is opening forms, processing things on them, and then closing them. After a while, the process starts getting some errors and eventually crashes.
Looking in Task Manager, I noticed that the process is hitting the limit of 10,000 GDI objects. This process must have a GDI object leak somewhere.
I downloaded GDIView.exe and noticed that the "All GDI" column keeps increasing over time, but the numbers of the listed types of GDI objects are reasonable and not increasing.
For example, see the screenshot below. GDIView.exe is showing 1,782 "Other GDI" objects. The rest of the columns show 4 Bitmap, 1 Region, and 0 other types of objects:
I downloaded a script "DumpGdi.txt" to help WinDbg show GDI object counts in a memory dump. The list only counts less than 100 GDI objects at any given time despite Task Manager and GDIView.exe showing a large number of GDI objects.
What are the GDI objects that are accumulating in the "All GDI" column in GDIView.exe? How would I get more visibility into them? I would like to know what is leaking, so I can know what to look for in my code.
Windows Task Manager shows the same high of GDI objects, too.
I do not see any managed object memory leaks. If there were managed objects, I could see them if I run a !DumpObj -type command in WinDbg, and I could use !GCRoot to find why they are being leaked. I would also expect to see a large number of Icon, Font, and possibly Bitmap objects, but these types do not appear to be a problem in the GDIView.exe output.
I have run a LeakTrack.exe in DebugDiag 2.x. The report output was not very helpful other than noting that the CLR could be a source of the leak.
I found the issue using the Microsoft Performance HUD tool. When I ran my test application using the Performance HUD, the tool showed me the issue was both CURSOR User objects and PAL GDI objects being leaked.
The Call Stack tab showed me the root cause of the leak was the following line of code which converts a bitmap object into an icon. This line creates a temporary GDI handle that needs to be disposed.
this.Icon = Icon.FromHandle(bitmap.GetHicon());
The fix is to dispose of the temporary handle:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);
IntPtr handle = IntPtr.Zero;
try
{
handle = bitmap.GetHicon();
this.Icon = Icon.FromHandle(handle);
}
finally
{
if (handle != IntPtr.Zero)
{
DestroyIcon(handle);
}
}