我有一个进程不断循环运行某些东西。它打开表单,处理其中的内容,然后关闭它们。一段时间后,该过程开始出现一些错误并最终崩溃。
查看任务管理器,我注意到该进程达到了 10,000 个 GDI 对象的限制。这个进程一定在某个地方有 GDI 对象泄漏。
我下载了GDIView.exe,注意到“所有GDI”列随着时间的推移不断增加,但列出的GDI对象类型的数量是合理的并且没有增加。
例如,请参阅下面的屏幕截图。 GDIView.exe 显示 1,782 个“其他 GDI”对象。其余列显示 4 个位图、1 个区域和 0 个其他类型的对象:
我下载了一个脚本“DumpGdi.txt”来帮助 WinDbg 显示内存转储中的 GDI 对象计数。尽管任务管理器和 GDIView.exe 显示大量 GDI 对象,但该列表在任何给定时间仅计数不到 100 个 GDI 对象。
GDIView.exe 中的“所有 GDI”列中累积的 GDI 对象是什么?我如何更好地了解它们?我想知道泄漏了什么,这样我就可以知道在我的代码中寻找什么。
Windows 任务管理器也显示相同高度的 GDI 对象。
我没有看到任何托管对象内存泄漏。如果存在托管对象,如果我在 WinDbg 中运行 !DumpObj -type 命令,我就可以看到它们,并且我可以使用 !GCRoot 来查找它们被泄漏的原因。我还希望看到大量图标、字体和可能的位图对象,但这些类型在 GDIView.exe 输出中似乎不是问题。
我在 DebugDiag 2.x 中运行了 LeakTrack.exe。除了指出 CLR 可能是泄漏源之外,报告输出没有太大帮助。
我使用 Microsoft Performance HUD 工具发现了该问题。当我使用性能 HUD 运行测试应用程序时,该工具向我显示问题是 CURSOR 用户对象和 PAL GDI 对象都被泄漏。
“调用堆栈”选项卡向我显示了泄漏的根本原因是以下代码行,它将位图对象转换为图标。此行创建一个需要处置的临时 GDI 句柄。
this.Icon = Icon.FromHandle(bitmap.GetHicon());
修复方法是处理临时句柄:
[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);
}
}