【问题标题】:Is it a mistake to check a `HWND` against `INVALID_HANDLE_VALUE`?检查 `HWND` 与 `INVALID_HANDLE_VALUE` 是否有误?
【发布时间】:2014-12-05 20:05:07
【问题描述】:

我和一位程序员同事进行了一场小辩论。他在代码中使用了以下成语:

HWND hWnd = SomeFunctionWhichReturnsAWindow();
if(hWnd != NULL && hWnd != INVALID_HANDLE_VALUE)
{
    // All good
}
else
{
    // Error
}

我告诉他,在我看来这是错误的方法,因为HWND 类型与INVALID_HANDLE_VALUE 定义无关,但他确信这是好的代码,因为有效的句柄永远不会相等到INVALID_HANDLE_VALUE,它的心情是“比抱歉更安全”。

那么,这是一个可以接受且正确的成语吗?

【问题讨论】:

  • 错误是一个大词。但可以肯定的是,窗口句柄永远不会有那个值,它只用于 kernel32 句柄。
  • @HansPassant 在实践中是的,但这不只是一个实现细节吗?
  • 如果您查看返回 HANDLE 的各种函数,您会发现其中一些返回 NULL(如 CreateThread),其中一些返回 INVALID_HANDLE_VALUE(如 CreateFile)。您必须检查文档以查看每个特定功能在失败时返回的内容。由于历史原因,返回值是如此不一致。选择这些值是为了与 16 位 Windows 兼容。 16 位函数 OpenFile、_lopen 和 _lcreat 在失败时返回 -1,因此 32 位 CreateFile 函数返回 INVALID_HANDLE_VALUE 以便于从 Win16 移植代码。

标签: c windows winapi


【解决方案1】:

HWNDINVALID_HANDLE_VALUE 进行比较是错误的。虽然,在实践中,这不是一个会伤害你的错误。

CreateWindowEx 保留为无效的唯一HWND 值是NULL。现在,INVALID_HANDLE_VALUE 不可能是有效的HWND 恰好是一个实现细节,但这只是实现细节。产生窗口句柄的函数CreateWindowEx 使用NULL 表示失败。这就是您需要知道的全部内容。

如果您希望与您的同事赢得争论,我建议您查看SomeFunctionWhichReturnsAWindow 并找出调用哪个 Win32 API 来生成HWND。然后查阅文档。这将告诉您NULL 是保留的无效值。

为了清楚起见,您绝对应该更改代码以单独针对 NULL 进行测试。

【讨论】:

    【解决方案2】:

    INVALID_HANDLE_VALUE 定义为 -1。无效的 HWND 定义为 0。任何 API 都不会在失败时返回 HWND(-1),因此检查 INVALID_HANDLE_VALUE 是没有意义的,它永远不会发生。

    但是,有些 API 接受保留的非零 HWND 值作为输入,因此也不能用作有效的 HWND 返回值:

    PeekMessage()GetMessage()

    如果 hWnd 为 NULL,(Peek/Get)Message 检索属于当前线程的任何窗口的消息,以及当前线程的消息队列中 hwnd 值为 NULL 的任何消息(参见 MSG 结构)。因此如果 hWnd 为 NULL,窗口消息和线程消息都会被处理。

    如果 hWnd 为 -1,(Peek/Get)Message 只检索当前线程的消息队列中 hwnd 值为 NULL 的消息,即 PostMessage 发布的线程消息(当 hWnd参数为 NULL) 或 PostThreadMessage。

    所以HWND(0)HWND(-1) 之间存在逻辑差异。事实上,由于这种差异,有效的 HWND 永远不会是 -1,因为消息循环永远无法为其检索消息。

    另外SetWindowPos() 也有一些保留值:

    hWndInsertAfter [输入,可选]
    类型:HWND

    在 Z 顺序中位于定位窗口之前的窗口句柄。此参数必须是窗口句柄或以下值之一。

    HWND_BOTTOM
    (HWND)1
    将窗口置于 Z 顺序的底部。如果 hWnd 参数标识了一个最顶层窗口,则该窗口将失去其最顶层状态,并被置于所有其他窗口的底部。

    HWND_NOTOPMOST
    (HWND)-2
    将窗口放置在所有非最顶层窗口的上方(即,在所有最顶层窗口的后面)。如果窗口已经是非最顶层窗口,则此标志无效。

    HWND_TOP
    (HWND)0
    将窗口置于 Z 顺序的顶部。

    HWND_TOPMOST
    (HWND)-1
    将窗口置于所有非最顶层窗口之上。窗口即使在被停用时也保持其最高位置。

    【讨论】:

    • 我还发现SetWindowPos 接收(HWND)1(HWND)-2,所以它们也不能是有效的HWNDs。不过,这不是在验证时检查这些内容的理由:)
    【解决方案3】:

    CreateWindowEx 和返回 HWND 的类似函数清楚地表明无效的 HWND 为 0。其他任何值都可能是有效的。

    因此,无论您假设什么,检查 INVALID_HANDLE_VALUE 都是 100% 错误的。

    做出诸如“这可能永远不会伤害”这样的假设是非常危险的,虽然目前是有效的,但将来您可能会习惯于假设类似的功能并非那么无辜。

    【讨论】:

      【解决方案4】:

      问题是windows句柄是否有效,为什么不直接使用IsWindow()

      【讨论】:

        【解决方案5】:

        HWND 不能用于与 INVALID_HANDLE_VALUE 相比,因为这两者是两个不同的东西。大多数资源(例如文件等)返回 HANDLE 并且必须与 INVALID_HANDLE_VALUE 进行比较,以确保资源可以被任何依赖它的函数使用。但是,HWND 指的是 Windows GUI 对象的句柄。因此,保存以将 HWND 值与 0 进行比较以获得无效的返回句柄,而不是使用也是 -1 的 INVALID_HANDLE_VALUE。

        此外,关于HANDLE类型的更多信息,您可以查看StackOverflow之前的答案以了解更多信息。

        句柄是特定于上下文的唯一标识符。根据具体情况, 我的意思是从一个上下文中获得的句柄不一定是 在任何其他也适用于 HANDLE 的任意上下文中使用。

        例如,GetModuleHandle 返回一个唯一标识符给 当前加载的模块。返回的句柄可用于其他 接受模块句柄的函数。不能赋予函数 需要其他类型的手柄。例如,你不能给出一个 从 GetModuleHandle 返回到 HeapDestroy 的句柄并期望它 做一些明智的事情。

        HANDLE 本身只是一个整数类型。通常,但不是 必然地,它是指向某些底层类型或内存的指针 地点。例如,GetModuleHandle 返回的 HANDLE 是 实际上是指向模块的基本虚拟内存地址的指针。 但是没有规定句柄必须是指针。一个手柄 也可以只是一个简单的整数(可能由 一些 Win32 API 作为数组的索引)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-10
          • 1970-01-01
          • 2010-12-26
          • 1970-01-01
          相关资源
          最近更新 更多