如何在 Windows 窗体中创建 GDI 泄漏!
Posted
技术标签:
【中文标题】如何在 Windows 窗体中创建 GDI 泄漏!【英文标题】:How can I create GDI Leaks in Windows Forms! 【发布时间】:2010-10-27 01:31:45 【问题描述】:我正在调查大型应用程序中的 GDI 资源泄漏。为了进一步了解这些问题是如何发生的,我创建了一个非常小的应用程序,我故意将其设置为“泄漏”。这是一个简单的用户控件,应该会创建 100 个 Pen 对象:
公共部分类TestControl:UserControl 私人列表钢笔=新列表(); 公共测试控制() 初始化组件(); for (int i = 0; i但是,当我创建对象的实例并将其添加到表单时,使用 TaskManager 查看我的应用程序时,我目前看到了大约 37 个 GDI 对象。如果我反复将新的 TestObject 用户控件添加到我的表单中,我仍然只能看到大约 37 个 GDI 对象。
这是怎么回事!我以为 System.Drawing.Pen 的构造函数会使用 GDI+ API 来创建新的 Pen,从而使用新的 GDI 对象。
我一定要疯了。如果我不能编写一个创建 GDI 对象的简单测试应用程序,我该如何创建一个泄漏它们的应用程序!
任何帮助将不胜感激。
最好的问候,科林 E。
【问题讨论】:
奇怪的问题,我同意。你有没有验证是否没有。如果您循环 少于 37 次,则 GDI 对象的数量会从 37 减少? 是的 - 37 个 GDI 对象似乎与简单测试应用程序本身的开销有关。与上述代码中的循环次数无关。我认为 OregonGhost(下)是关于 GDI+ 不使用 GDI 句柄的东西,我认为它确实使用了!我希望有一些文档可以验证这一点。 【参考方案1】:GDI+ 是否使用 GDI 句柄?我不确定,虽然我在某处读到有一个依赖于裸 GDI 的 .NET System.Drawing 实现。
但是,也许您可以尝试使用像 AQTime 这样的分析器来查找泄漏。
您如何确定您的大型应用会泄漏 GDI 句柄?任务管理器中的计数大吗?如果是这样,您总是使用 GDI+,还是同时使用 GDI?如果您多次创建控件,您的测试应用 GDI 处理计数是否会增加?
【讨论】:
感谢您的回复。是的,我怀疑 GDI+ 并不总是使用 GDI 句柄。我确信大型应用程序会泄漏 GDI 句柄。它使用大量(> 500)并且随着时间的推移而增加。至于我们是否一直使用 GDI+,我们只使用 System.Drawing,我认为它只使用 GDI+ - 对吗?谢谢。 ... 与此相关,如果 System.Drawing.Pen 不使用 GDI 句柄,我通过 Disposing 释放什么资源?抱歉,如果我的一个问题变成了三个问题! 我其实不确定。如前所述,我在某处读到有一个使用 GDI 的 System.Drawing 实现,而人们通常希望 System.Drawing 使用 GDI+。可能已经在 Windows CE 或其他东西上。我也不确定创建窗口句柄(窗体和控件)是否可以增加 GDI 句柄数。在许多情况下,当我泄漏某种句柄时,是因为泄漏了控件而不是 GDI+ 的东西。 @Pen.Dispose:您正在释放 System.Drawing 资源,您没有责任找出它到底是什么。可以是底层 GDI+ 对象、GDI 句柄或其他东西。 这个答案是正确的;虽然他不确定:PSystem.Drawing.Pen
是 GDI+ Pen 的包装器(尽管这是一个可能随时更改的实现细节)。这解释了为什么您没有看到 GDI 资源被使用,它没有使用 GDI 资源。另一方面,System.Drawing.Pen
确实实现了IDisposable
,因此当您完成对象时,您是否需要调用Dispose
。【参考方案2】:
您并没有真正泄漏示例中的资源。从您的 Load 事件中删除此代码:
for (int i = 0; i < 100; i++)
pens.Add(new Pen(new SolidBrush(Color.FromArgb(255, i * 2, i * 2, 255 - i * 2))));
您的 Paint 事件处理程序应如下所示:
void TestControl_Paint(object sender, PaintEventArgs e)
for (int i = 0; i < 100; i++)
e.Graphics.DrawLine(new Pen(new SolidBrush(Color.FromArgb(255, i * 2, i * 2, 255 - i * 2))), 0, i, Width, i);
现在您将在每次绘制调用中泄漏。开始最小化/恢复您的表单并查看 GDI 对象天空火箭...
希望这会有所帮助。
【讨论】:
嗨 Denis ... 不错的尝试,但您实际测试过吗?我试了一下,我的应用程序仍然只使用了少数 GDI 对象。这没有意义!【参考方案3】:如果你想从 .NET 中泄露一个 GDI 对象,那么只需创建一个 GDI 对象而不释放它:
[DllImport("gdi32.dll", EntryPoint="CreatePen", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
private static extern IntPtr CreatePen(int fnStyle, int nWidth, int crColor);
CreatePen(0, 0, 0); //(PS_SOLID, 0=1px wide, 0=black)
Blingo blango,你在泄漏 GDI 笔。
我不知道为什么您要创建 GDI 泄漏。但您的问题是如何从 WinForm 创建 GDI 泄漏 - 就是这样。
【讨论】:
您还应该提到在本机代码 (P/Invoke) 中有效但在托管 GDI/GDI+ 实现中无效的原因。我相信这与 .NET 垃圾收集 GDI/GDI+ 资源有关。 @IDWMaster:.NET 垃圾收集器无法“收集”本机资源(例如 GDI 句柄)。任何执行 P/Invoke 以分配 GDI 句柄的 .net 代码也负责执行 P/Invoke 以释放它们。幸运的是,.NET 框架中的每个对象都小心翼翼地做到这一点。垃圾收集器与此无关。 GC 可以在对象上调用析构函数,这可以;反过来释放非托管资源。 一个对象的终结器可能为你调用.Dispose
- 但这纯粹是一个“save-your-ass bonus”的东西。这不是必需的;对象作者不需要通过为您调用Dispose
来帮助您自己清理。无论哪种方式,释放托管资源的是Dispose
,而不是垃圾收集器。垃圾收集器不知道如何释放非托管资源;它甚至不知道如何调用Dispose
。如果对象的终结器没有调用 Dispose,那么你甚至都无法获得拯救你的安全网。【参考方案4】:
我认为编译器只使用一个句柄。
如果我在 delphi 中创建了很多字体,我只会占用内存
但是如果我使用 WinAPI CreateFont()
我会使用 GDI 对象。
【讨论】:
【参考方案5】:在表单上创建两个按钮。在每个按钮内,添加以下代码。一键将 Dispose 方法注释掉。
Form _test = null;
for (int i = 0; i < 20; i++)
_test = new Form();
_test.Visible = false;
_test.Show();
_test.Hide();
_test.Dispose();
Dispose 被注释掉的按钮显示泄漏。另一个显示 Dispose 导致 User 和 GDI 句柄保持不变。
This 可能是我找到的最好的解释它的页面。
【讨论】:
【参考方案6】:我认为下面的博客可能已经回答了这个问题:
Using GDI Objects the Right Way
未显式释放的 GDI 对象应通过它们的终结隐式释放。 (Bob Powell 在GDI+ FAQ 中也提到过这个问题)
但我怀疑 CLR 垃圾收集器是否可以如此快速地删除 GDI 资源,以至于我们甚至无法从 TaskManager 中看到内存使用情况的变化。也许当前的 GDI+ 实现不使用 GDI。
我已尝试使用以下代码生成更多 GDI 对象。但是我仍然看不到 GDI 句柄数量的任何变化。
void Form1_Paint(object sender, PaintEventArgs e)
Random r = new Random();
while (true)
for (int i = 0; i < 100; i++)
e.Graphics.DrawLine(
new Pen(new SolidBrush(Color.FromArgb(r.Next()))), 0, i, Width, i);
【讨论】:
以上是关于如何在 Windows 窗体中创建 GDI 泄漏!的主要内容,如果未能解决你的问题,请参考以下文章
System.Drawing.Bitmap.GetHicon() 上的 GDI 对象泄漏
如何在 Windows 窗体应用程序中创建全局错误处理程序?