【问题标题】:Does DeleteDC automatically unselect objects?DeleteD 会自动取消选择对象吗?
【发布时间】:2015-02-09 22:30:50
【问题描述】:

我是否总是需要调用 SelectObject 将原始对象恢复到 DC(并使我的对象可用于删除),即使我将要删除 DC...?

例如

  // Create DC
  HBITMAP hBitmap = CreateCompatibleBitmap (hDC, rect.Width(), rect.Height());
  HGDIOBJ hOldBitmap = SelectObject(hMemDC, hBitmap);


  // ... Do some other stuff with the DC

  // DO I NEED THIS LINE HERE???
  SelectObject(hMemDC, hOldBitmap);

  // Tidy up
  DeleteDC(hMemDC);
  DeleteObject(hBitmap);  // This DOES return TRUE even without the SelectObject line...

“DeleteDC”是否会自动取消选择其中选定的对象,以便在删除 DC 后可以删除它们?

谢谢

【问题讨论】:

  • 当然。这并不能防止您的代码泄漏 hOldBitmap。
  • 我假设它为 NULL,因为我已经创建了 DC,而我的 hBitmap 是我选择的第一个(唯一)位图。将其选回 DC 的唯一原因是确保未将 hBitmap 选入 DC,因此可以将其删除……但您是说没有必要这样做。谢谢!
  • “我假设它是 NULL。”你确定这是一个安全的假设吗?
  • DC 在创建时会获得默认的 1x1x1 位图。如果你不把它放回去,这个泄漏。
  • @Jonathan - 谢谢,知道这很有用。还有吉姆 - 不,看起来这不是一个安全的假设......!

标签: winapi


【解决方案1】:

DC 不维护选定对象的历史记录。例如,想一想如果您在绘制某些东西时要SelectObject() 使用不同的字体或刷多次会发生什么。 DC 只知道 current 对象,而不知道任何先前的对象。这就是为什么您必须始终使用SelectObject() 来恢复您替换的任何对象,然后再释放 DC。 DeleteDC() 不会为您进行恢复。

这在文档中有明确说明:

SelectObject function:

此函数返回先前选择的指定类型的对象。 应用程序在使用新对象完成绘制后,应始终将新对象替换为原始默认对象。

Operations on Graphic Objects:

这些函数中的每一个都返回一个标识新对象的句柄。在应用程序检索到句柄后,它必须调用 SelectObject 函数来替换默认对象。但是,应用程序应保存标识默认对象的句柄,并在不再需要时使用此句柄替换新对象。 当应用程序用新对象完成绘制后,它必须通过调用 SelectObject 函数恢复默认对象,然后通过调用 DeleteObject 函数删除新对象。未能删除对象会导致严重的性能问题。

DC 拥有它最初创建时使用的对象。当它被释放时,它将相应地释放任何 当前 选定的对象,因为它期望原始对象。未能恢复原始对象将导致代码泄漏并可能出现其他故障。

如果您要在绘图时替换/恢复多个对象,请考虑使用SaveDC()RestoreDC() 来简化原始对象的恢复:

Saving, Restoring, and Resetting a Device Context

【讨论】:

  • 我知道 DC 不会保留历史记录,我的问题更多是关于当对象仍被选入 DC 时无法删除的事实。问题是,如果我调用 DeleteDC,这会自动为我从 DC 中选择一些东西(从而允许我删除它们),还是我需要自己做?但看起来我实际上是出于错误的原因问这个问题 - 我确实需要将东西恢复到 DC 中,不是为了让我的对象可以删除,而是让 DC 可以在我删除它的 OWN 对象后删除它把它们放回去。
  • ...我通常会自己整理好,但我在我的项目中发现了一些代码,其中 DC 被删除而没有先恢复原始位图,我想知道这是否会导致 GDI资源泄漏。答案是肯定的,但不是我想的那样!
【解决方案2】:

我已经对这个主题进行了自己的调查。根据他们的说法,标记的答案至少在以下声明中并不准确:

一个 DC ... 会在它被释放时相应地释放任何当前选定的对象,因为它期望原始对象。

当您将刚刚创建内存位图选择到刚刚创建内存 DC 时,内存 DC 不拥有该位图(至少在内存管理的意义上) - 它将它标记为“已使用”,因此它不能被 DeleteObject 调用删除(可能是通过增加位图的一种引用计数)。如果您先删除内存 DC - 它不会删除内存位图:GetGuiResources(GR_GDIOBJECTS) 返回的对象计数减 1,即仅适用于 DC 本身,而不是 2(对于DC 和位图)。随后的DeleteObject(hMemBitmap) 调用相应地减少了 GDI 对象计数。

至于in this comment提到的“默认1x1位图的泄漏”,似乎也是错误的:这个位图不是为每个内存DC单独创建的。相反,它是Raymond Chen很好解释的所谓“股票位图”:根据his "The mysterious stock bitmap" article,这个位图是GDI用于各种目的的单例,它是唯一可以选择进入许多DC的位图。除了将其选择到 DC 之外,您无法删除它或以任何方式使用它。尽管DeleteObject 在此位图的句柄上返回 TRUE,但它不会影响 GDI 对象计数。在删除该 DC 之前将该位图选择回内存 DC 只是一种好习惯,实际上并不是必需的:选择它会增加其使用引用计数,然后删除该 DC 会减少该引用计数,这会导致没有增益 (参考值保持与从内存 DC 中“取消选择”库存位图后的值相同)。

我做了一个 PoC 测试程序,并在不同版本的 Windows(从 WinXP SP3 到最新的 Win10 更新)中进行了检查 - 结果非常一致。

总结一下:

  • 原始问题的答案是“是的,确实如此”

  • // DO I NEED THIS LINE HERE??? 问题的答案是“不,你不知道 - 前提是你确定旧位图的句柄是库存位图的句柄”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-20
    • 2018-07-15
    • 1970-01-01
    • 1970-01-01
    • 2013-12-18
    相关资源
    最近更新 更多