【问题标题】:How to check the "type" of a Win32 handle at runtime如何在运行时检查 Win32 句柄的“类型”
【发布时间】:2014-09-13 10:42:43
【问题描述】:

我必须在 C++11 中为 HFONT、HWND、HMODULE 等 Win32 句柄编写一个引用计数包装类。我想使用一个隐式转换为所有句柄类型(都是 void* typedef)的单个 WinHandle 类。不幸的是,所有句柄都有不同的函数来销毁底层对象,例如 DestroyWindow()、CloseHandle()、DeleteObject() 等等,因此我需要为每个句柄类型使用不同的类来实现适当的销毁函数。 (那将是 47 个类 + 基类,包括用户对象、gdi 对象和内核对象)

那么有没有办法在运行时确定句柄是哪个“类型”,或者更确切地说,需要调用哪个函数? (在文档中我只找到了 isWindow() 函数)

我已经考虑过使用 RTTI 或调用所有删除函数,直到其中一个成功。 RTTI 不起作用,因为所有 HANDLE 类型都是 void* 的类型定义,因此是相同的。后者可能有效,但所有句柄必须是唯一的才能正常工作(任何 GDI 句柄都不能与用户句柄或内核句柄具有相同的值),否则可能会导致错误和内存泄漏

【问题讨论】:

  • 句柄不一定是void * typedef。我相信是 STRICT 给了他们自己的类型(并且有充分的理由)。
  • 这些关闭/删除函数中的很多/大多数(全部?)不是采用HANDLE 并返回BOOL 吗?如果是这样,您可以将删除函数作为BOOL (*deleter)(HANDLE) 传递给句柄包装器的构造函数,将其存储在您的包装器对象中并在适当时调用它。
  • NtQueryObject 可能会帮助你,但如果我没记错的话,不要使用 USER 或 GDI 句柄。
  • 即使有这样的检测功能,也无济于事。假设IsWindow()IsFont() 都返回TRUE。 (这是可能的,因为唯一的保证是没有两个窗口具有相同的句柄,但一个窗口句柄可能等于一个字体句柄。)您仍然不知道您是否有一个窗口句柄或一个字体句柄。你需要知道你有什么样的手柄。 (STRICT 宏可能会有所帮助。)
  • 您可能需要WinHandle<HFONT>WinHandle<HWND> 等。但是您为什么需要引用计数的包装器?已经有DuplicateHandle

标签: c++ winapi c++11


【解决方案1】:

如果您要查找的唯一内容是引用计数句柄,为什么不直接使用 shared_ptr

例如:

shared_ptr<void> file( CreateFile(L"la.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL), CloseHandle);   

DWORD written;
WriteFile(file.get(), //get handle
          "ABC\r\n", 
          5,
          &written, 
          NULL);

您的代码不会占用太多空间,您也不必编写 40 个类。


您可以通过为每个关闭的类型定义一些函数来避免每次都传递相关的关闭函数,例如:

auto make_handle_CloseHandle = [](HANDLE h){ return  (shared_ptr<void>(h,CloseHandle)); };

auto file = make_handle_CloseHandle(CreateFile(L"la.txt", /*same ...*/));

DWORD written;
WriteFile(   file.get(), //get handle
            "ABC\r\n", 
            5,
            &written, 
            NULL);

并将其放在相关命名空间下的某个头文件中,这样您就不必每次都键入关闭函数的名称,并且这些函数的用户将知道调用哪个函数(根据make_handle_*名称)哪个可能比尝试仅通过句柄自动识别句柄的类型更安全。

【讨论】:

  • 谢谢你的回答,我也一定会调查的。如果你问我每次都将破坏函数传递给 ctor 有点笨重,但我相信以某种方式利用 std::shared_ptr 会更好,因为它的弧算法很可能比我的更快。也许我可以从 shared_ptr 派生并使用 STRICT 宏来区分不同的句柄类型
  • @Cody227 你可以轻松避免每次都通过破坏者函数,请看我的编辑。
  • 这个答案需要一些std::make_shared的爱。
  • @rubenvb 我很乐意改进答案,但正如文档所述 此函数通常用于替换构造 std::shared_ptr(new T(args.. .)) 来自调用 new 返回的原始指针的共享指针。 在这种情况下,我们不是通过调用 new 创建原始指针,而是使用一些WIN32函数。另一件事(可能相关)是我在使用make_shared 时没有找到提供自定义删除器的方法(可能是因为它假定需要newdelete
  • 嗯,你是对的。我弄错了(例如this question 解释得很好......)。抱歉打扰了!
【解决方案2】:

由于 CreateFile 的失败结果是 INVALID_HANDLE_VALUE (-1) 我不确定这是否是一个好的解决方案。

https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea

与其使用旨在引用内存地址的东西,不如实现某种 shared_resource 或 unique_resource 类型,它接受模板参数或封装包装类型特征的单个参数可能是要走的路。

我的看起来像这样:

template <typename TRAITS>
struct SharedHandle
{
....
}

并且是这样使用的:

struct TestTraits
{
    using HandleType = int;
    static constexpr HandleType INVALID = 0;
    static void Close(HandleType& h)
    {
        h = INVALID;
    }
};

using SharedHandleTestType = SharedHandle<TestTraits>;


TEST(ClvLib_SharedHandle_Tests, SharedHandle_default)
{
    auto tt = SharedHandleTestType();

    EXPECT_FALSE(tt);
}

TEST(ClvLib_SharedHandle_Tests, SharedHandle_good)
{
    auto tt = SharedHandleTestType(1);

    EXPECT_TRUE(tt);
}

TEST(ClvLib_SharedHandle_Tests, SharedHandle_use)
{
    auto tt = SharedHandleTestType(1);

    auto result = [](int t)->int {return t + 1; }(tt);
    auto expected = tt.Get() + 1;

    EXPECT_EQ(expected, result);
}

TEST(ClvLib_SharedHandle_Tests, SharedHandle_copy1)
{
    auto tt = SharedHandleTestType(1);
    auto t2 = tt;

    EXPECT_TRUE(tt);
    EXPECT_TRUE(t2);

    tt = 0;

    EXPECT_FALSE(tt);
    EXPECT_TRUE(t2);
}

TEST(ClvLib_SharedHandle_Tests, SharedHandle_copy2)
{
    auto tt = SharedHandleTestType(1);
    auto t2 = tt;

    EXPECT_TRUE(tt);
    EXPECT_TRUE(t2);

    tt = 0;

    EXPECT_FALSE(tt);
    EXPECT_TRUE(t2);

    t2.Release();

    EXPECT_FALSE(tt);
    EXPECT_FALSE(t2);
}

对于 Windows 文件句柄:

struct WinHandleTraits_IHV
{
    using HandleType = HANDLE;
    static constexpr HandleType INVALID = INVALID_HANDLE_VALUE;
    static void Close(HandleType& h)
    {
        CloseHandle(h);
    }
};

using WinFileHandle = SharedHandle<WinHandleTraits_IHV>;

【讨论】:

    猜你喜欢
    • 2011-10-22
    • 2020-06-20
    • 2014-07-30
    • 1970-01-01
    • 2013-11-02
    • 2014-06-02
    • 2011-12-04
    • 1970-01-01
    • 2012-01-27
    相关资源
    最近更新 更多