3

I have to write a reference counted wrapper class in C++11 for Win32 handles like HFONT, HWND, HMODULE and so on. I want to use a single WinHandle class that implicitly casts to all handle types (which are all a void* typedef). Unfortunately all handles have different functions to destroy the underlying object for example DestroyWindow(), CloseHandle(), DeleteObject() and so forth, thus i'd need a different class for every handle-type to implement the appropriate destroy-function. (that would be 47 classes + base class including user objects, gdi objects and kernel objects)

So is there a way to determine at runtime of which "type" the handle is or rather which function needs to be called? (In the documentation I only found the isWindow() function)

I already thought about using RTTI or calling all delete-functions until one of them succeeds. RTTI won't work because all HANDLE types are typedefs of void* and thus the same. The latter might work, but all handles must be unique for it to work properly (no GDI handle can ever have the same value as a user handle or kernel handle) otherwise it might cause bugs and memory leaks

Grisu47
  • 530
  • 5
  • 16
  • 1
    Handles are not necessarily a `void *` typedef. I believe it's `STRICT` that gives them their own types (and for good reason). – chris Jul 22 '14 at 14:39
  • 3
    Don't many/most (all?) of those close/delete functions take a `HANDLE` and return a `BOOL`? If so, you could pass the deleting function to the constructor of your handle wrapper as a `BOOL (*deleter)(HANDLE)`, store it in your wrapper object and call it when appropriate. – Michael Jul 22 '14 at 14:42
  • NtQueryObject may helps you, but if I remember correctly dont work with USER nor GDI handles. – Xearinox Jul 22 '14 at 14:45
  • 6
    Even if such a detection function existed, it wouldn't help you. Suppose `IsWindow()` and `IsFont()` both return `TRUE`. (Which is possible, because the only guarantee is that no two windows will have the same handle, but a window handle might be equal to a font handle.) You still don't know whether you have a window handle or a font handle. You need to know what kind of handle you have. (The `STRICT` macro may help.) – Raymond Chen Jul 22 '14 at 15:10
  • You'd probably want a `WinHandle`, `WinHandle` etc. But why do you want a refcounted wrapper? There's already `DuplicateHandle`. – MSalters Jul 23 '14 at 07:04
  • I am baffled as to why Win32 doesn't have a function called IsConsoleHandle. – user13947194 Jun 06 '23 at 20:40

2 Answers2

2

If the only thing you're looking for is a reference counted handle, why not just use shared_ptr ?

E.g :

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);

It doesn't have a big foot print int your code and you won't have to write 40 classes.


You can avoid passing the relevant close function each time by defining some function for each type of closing e.g :

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);

And put it in some header file under a relevant namespace that way you won't have to type the close function's name each time and users of those functions will know which function is called (according to the make_handle_* name) which might make thing safer than trying to automagically identify the handle's type just by the handle.

Scis
  • 2,934
  • 3
  • 23
  • 37
  • thx for your answer, I will definitely look into that as well. If you ask me it's a bit bulky to pass the destroyer function to the ctor everytime, but I'm sure it would be better to utilize std::shared_ptr in some way, as it's arc algorithm will most likely be faster than mine. Maybe I can derive from shared_ptr and use the STRICT macro to distinguish beetween different handle-types – Grisu47 Jul 22 '14 at 17:33
  • @Cody227 you can easily avoid passing the destroyer function each time, please see my edit. – Scis Jul 23 '14 at 09:20
  • This answer needs some [`std::make_shared`](http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared) love. – rubenvb Jul 23 '14 at 11:31
  • @rubenvb I'd be happy to improve the answer but as the documentation states _This function is typically used to replace the construction std::shared_ptr(new T(args...)) of a shared pointer from the raw pointer returned by a call to **new**._ And in this case we're not creating a raw pointer by calling `new` but by using some WIN32 function. Another thing (that is probably related) is that I didn't find a way to supply a custom deleter when using `make_shared` (probably because it assumes `new` and `delete` are needed) – Scis Jul 23 '14 at 13:16
  • Hmm, you're right. I am mistaken (e.g. [this question](http://stackoverflow.com/questions/19599749/custom-deleters-for-stdshared-ptrs) explains it quite well...). Sorry for the noise! – rubenvb Jul 23 '14 at 13:17
  • @rubenvb NP :) and thanks for linking to that question. – Scis Jul 23 '14 at 13:20
2

Since the failure result for CreateFile is INVALID_HANDLE_VALUE (-1) I'm not certain this is a good solution all in all.

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

Rather than use something intended to refer to a memory address, implementing some sort of shared_resource or unique_resource type, which accepts template arguments or a single argument that encapsulates the characteristics of the wrapped type is probably the way to go.

Mine looks something like this:

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

and is used like this:

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);
}

For Windows file handles:

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>;
CLV
  • 21
  • 2