【问题标题】:Move semantics and windows handles. DeleteObject-safe handle?移动语义和窗口句柄。 DeleteObject 安全句柄?
【发布时间】:2012-12-12 01:11:04
【问题描述】:

Windows 中有某种 NULL 句柄吗?如果我通过CreateCompatibleBitmap() 创建一个bmp 并通过DeleteObject() 删除它并想使用移动语义,我想确保位图没有被破坏。因此,我必须将HBITMAP 设置为可以安全删除的值。喜欢delete nullptr。

【问题讨论】:

  • 一个HBITMAP 是一个void *,所以nullptr 是一个完全可赋值的值。

标签: c++ windows bitmap move-semantics


【解决方案1】:

首先是坏消息。由于历史原因,Windows API 中没有普遍有效的“无效句柄”值。 Windows 中的不同子系统将NULL 或INVALID_HANDLE_VALUE 视为无效句柄值(用于返回无效句柄值和获取一个)。 Related article on Old New Thing.

但是,好消息是,尽管您仍然需要为意外的返回值做好准备(除非您仔细阅读所使用的每个函数的文档),但仍然始终提供无效值“工作”在实践中。
您可能没有使用正确的指定“无效”值,但它仍然无效,因此该函数将失败。您的应用程序不会崩溃,除了浪费几个 CPU 周期外,没有其他负面后果。

因此,请继续使用NULL(或nullptr),您可以使用它。如果不出意外,这对于稍后阅读您的代码的人来说很直观。在您的具体示例中,这也是正确的,因为 GDI 函数假定 NULL 无效。

您几乎可以相信NULL 和INVALID_HANDLE_VALUE 都是无效值。虽然我不知道有效的HANDLE 必须是非零的要求(如文档中明确说明的那样),但实际上它们总是如此。我敢打赌,你永远不会找到值为零的句柄(只需尝试使用 Sysinternals 的 handle 工具,你计算机上的单个进程的句柄可能不会低于 20)。

但是即使假设NULL 可以是一个有效的句柄值,你必须考虑到一些句柄在main 被调用或全局构造函数运行之前已经打开和关闭,你真的别无选择.这意味着假设 NULL 可能是一个有效的句柄,并且假设它在您的程序运行时仍然有效,那么这个假设的句柄偶然属于与 API 函数兼容的类型的可能性非常低。

另一方面,有人可能会争辩说,应用程序可能打开了(unsigned) -1 句柄,从而将INVALID_HANDLE_VALUE 呈现为一个有效 值。

除非您正在泄漏句柄,否则我无法想象您将如何获得这么多打开的句柄。但更重要的是,早在达到这个数字之前,您可能会在 64 位系统上耗尽内存,而在 32 位系统上您肯定会耗尽地址空间。
如果INVALID_HANDLE_VALUE 作为一个有效句柄曾经成为一个问题,那么您的问题就会严重得多。

【讨论】:

  • 你对应该是@​​987654339@还是if (x) DeleteObject(x)有意见吗?
  • INVALID_HANDLE_VALUE 是许多 API 的有效输入。它被定义为(HANDLE)(-1) 和so is the pseudo-handle for the current process。 Raymond Chen points out the potential for confusion.
  • 此外,如果您传入NULL 句柄,Windows 可以防御性地实现并且不会崩溃,但应用程序验证程序确实会验证句柄。要获得干净的健康证明(和徽标认证),您不应该仅仅因为它不会崩溃而通过不良处理。您还将获得一堆内核调试输出,从而更难找到更严重的问题。说“没有负面后果”是错误的。
  • 哦,嘿,您已经链接了同样的旧新事物文章。太糟糕了,你在回答中反驳了它。
  • @Ben:你根本就错了,将“无效的句柄值”与“无效的参数”混为一谈。 例如,IsBadReadPtr 函数必须支持无效指针值作为参数,因为这就是函数的全部意义所在。在这种情况下(在 DeleteObject 的情况下,它已明确记录,在我的回答中引用)类型的无效值是有效的参数值。
【解决方案2】:

没有这样的安全值。将除有效的HGDIOBJ 之外的任何内容(特别是“逻辑笔、画笔、字体、位图、区域或调色板的句柄”)传递给DeleteObject 都会破坏合同并可能使您的程序崩溃。或者它可能会闯入调试器,尤其是在操作系统的检查版本上。或者它可以用日志消息填充您的硬盘。否则可能会导致您无法通过 AppVerifier 并阻止徽标认证。或者它可能会为您的进程触发“appcompat”规则并禁用新的 Windows 功能“以实现向后兼容性”。不要这样做。

您可以使用0 作为占位符,但如果您的句柄是0,请测试该值并且不要调用DeleteObject。这与if (p) delete p; 形成鲜明对比,其中前面的测试被认为是浪费代码。

【讨论】:

  • 我想你可能在想LocalFree,把它和DeleteObject混为一谈?
  • @Alf:有时DeleteObject 能够返回有用的结果并避免崩溃这一事实并不会改变违反合同的情况。将除有效句柄之外的任何内容传递给DeleteObject 是一个错误。特别是 DeleteObject(0) 是不允许的,根据先决条件。
  • 我认为,当您引用“先决条件”时,您指的是 annotations 而不是文档。嗯,你知道的,MessageBox 的注解是错误的。如果DeleteObject 的注释与文档相矛盾,那么它们也是错误的。我希望你不是指注释。但如果你是,请记住它们非常不可靠。
  • @Alf:约定是当您传递“逻辑笔、画笔、字体、位图、区域或调色板的句柄”时。作为 hObject 参数,函数将按描述运行并产生返回值。
  • @Alf:顺便说一句,您选择提及LocalFree 非常有趣,因为这有一个很好的例子说明当NULL 被允许时文档的样子:“如果@ 987654336@参数为NULL,LocalFree忽略参数返回NULL。"请注意,包含“忽略参数”的措辞是为了表达没有负面影响。
【解决方案3】:

通常 0 是无效句柄,类似于空指针。

例如,CreateBitmap 如果失败,则返回 0 作为无效的位图句柄。

因此,您可以使用空句柄安全地调用 DeleteObject。

来自documentation of DeleteObject:

如果指定的句柄无效或当前被选入 DC,则返回值为零。

一个例外是文件句柄,由CreateFile 返回,其中INVALID_HANDLE_VALUE 定义为-1。

【讨论】:

  • CreateCompatibleBitmap 在失败时返回 NULL。所以,我认为最好设置为 nullptr
  • @Chubsdad:在 C++ 直到并包括 C++03 之前,0 和 NULL 之间没有可能的区别,除了后者只能通过头文件定义获得,而且它可以指定int 以外的大小。但是,使用nullptr 您要求将句柄类型定义为指针,而0 没有这样的要求。在实践中 HGDIOBJ 当前是一个指针,但 AFAIK 不能保证它是,所以使用 nullptr 不是一个好主意。
  • -1。我认为这行文档旨在描述可能导致函数失败的条件,而不是保证函数在这些条件下总是会安全失败。
  • 在上下文中阅读,本意是“如果返回值为零,则指定的句柄无效”,并不保证函数不会引发异常或导致其他有害副作用. IMO,鉴于措辞模棱两可或可疑,最好进行防御性编程。
  • @Alf:句柄实现的细节(是的,我知道它是一个索引)确实控制了未定义行为的实际结果。但是,它们不会影响未定义行为的边界。即使将NULL 句柄与有效句柄区分开来非常容易,但这并不排除负面影响。事实上,调试环境(即应用程序验证程序、已检查的 Windows 构建等)经常在应用程序使用无效句柄时故意使应用程序崩溃。即使通过 AppVerify 不需要以某些方式进行营销,...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-11
  • 2017-10-02
  • 1970-01-01
  • 1970-01-01
  • 2020-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多