如何在 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 句柄或其他东西。 这个答案是正确的;虽然他不确定:P System.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 窗体应用程序中创建全局错误处理程序?

在 Windows 窗体中创建条形图

使用GDI时如何确定是否有内存泄漏

在 Windows 窗体 C# 中创建 POI 映射。如何将图标(图像)放在地图上(图片框中的现有图像)?

在 Windows 窗体中创建类似控制台的信息显示