【问题标题】:How can I create GDI Leaks in Windows Forms!如何在 Windows 窗体中创建 GDI 泄漏!
【发布时间】: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 句柄的东西,我认为它确实使用了!我希望有一些文档可以验证这一点。

标签: winforms 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);
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-15
        • 2014-02-10
        • 2013-06-10
        • 2010-10-03
        • 2012-01-08
        • 2011-12-23
        • 1970-01-01
        相关资源
        最近更新 更多