12

I've recently posted a general question about RAII at SO. However, I still have some implementation issues with my HANDLE example.

A HANDLE is typedeffed to void * in windows.h. Therefore, the correct shared_ptr definition needs to be

std::tr1::shared_ptr<void> myHandle (INVALID_HANDLE_VALUE, CloseHandle);

Example 1 CreateToolhelp32Snapshot: returns HANDLE and works.

const std::tr1::shared_ptr<void> h
    (CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL), CloseHandle);

As I use void in the definition (what is the correct way?) problems go on, when I try to call some more winapi commands with this pointer. They functionally work, but are ugly and I am sure that there has to be a better solution.

In the following examples, h is a pointer which was created via the definition at the top.

Example 2 OpenProcessToken: last argument is a PHANDLE. medium ugly with the cast.

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
    (PHANDLE)&h);

Example 3 Process32First: first argument is a HANDLE. REALLY ugly.

Process32First(*((PHANDLE)&h), &pEntry);

Example 4 simple comparison with a constant HANDLE. REALLY ugly.

if (*((PHANDLE)&h) == INVALID_HANDLE) { /* do something */ }

What is the correct way to create a proper shared_ptr for a HANDLE?

Community
  • 1
  • 1
Etan
  • 17,014
  • 17
  • 89
  • 148
  • 1
    Not a complete answer, but one can slightly reduce "ugliness" of using `void` type by using `std::remove_pointer::type` from `type_traits` instead. – Andrey Starodubtsev Aug 21 '20 at 09:11

4 Answers4

11

Example 1 is OK

Example 2 is wrong. By blindly casting to PHANDLE, the shared_ptr logic is bypassed. It should be something like this instead:

HANDLE h;
OpenProcessToken(...., &h);
shared_ptr<void> safe_h(h, &::CloseHandle);

or, to assign to a pre-exising shared_ptr:

shared_ptr<void> safe_h = ....
{
  HANDLE h;
  OpenProcessToken(...., &h);
  safe_h.reset(h, &::CloseHandle);
}//For extra safety, limit visibility of the naked handle

or, create your own, safe, version of OpenProcessToken that returns a shared handle instead of taking a PHANDLE:

// Using SharedHandle defined at the end of this post
SharedHandle OpenProcess(....)
{
    HANDLE h = INVALID_HANDLE_VALUE;
    ::OpenProcessToken(...., &h);
    return SharedHandle(h);
}

Example 3: No need to take these detours. This should be ok:

Process32First(h.get(), ...);

Example 4: Again, no detour:

if (h.get() == INVALID_HANDLE){...}

To make things nicer, you could typedef something like:

typedef shared_ptr<void> SharedHandle;

or better yet, if all handles are to be closed with CloseHandle(), create a SharedHandle class wrapping a shared_ptr and automatically providing the right deleter:

// Warning: Not tested. For illustration purposes only
class SharedHandle
{
public:
  explicit SharedHandle(HANDLE h) : m_Handle(h, &::CloseHandle){};
  HANDLE get()const{return m_Handle.get();}

  //Expose other shared_ptr-like methods as needed
  //...

private:
  shared_ptr<void> m_Handle;
};
Éric Malenfant
  • 13,938
  • 1
  • 40
  • 42
  • is there some possibility to delete the unsafe `HANDLE` after converting it to a safe one in your first example 2 code snippet? – Etan Oct 13 '09 at 20:21
  • You could create a function wrapping OpenProcessHandle() (I added this to the post) or do the same thing than in the second snippet, with shared_h initialized to INVALID_HANDLE_VALUE – Éric Malenfant Oct 13 '09 at 20:35
3

Don't bother with shared_ptr for that, use ATL::CHandle.

Here is why:

  • When you see CHandle you know that it's a RAII wrapper for a handle.
  • When you see shared_ptr<void> you don't know what it is.
  • CHandle doesn't make an ownership shared (however in some cases you may want a shared ownership).
  • CHandle is a standard for a windows development stack.
  • CHandle is more compact than shared_ptr<void> with custom deleter (less typing/reading).
Sergey Podobry
  • 7,101
  • 1
  • 41
  • 51
  • Can you expand on that? The [documentation](https://msdn.microsoft.com/en-us/library/5fc6ft2t.aspx) doesn't say much, but it looks more like a unique_ptr, I don't see how CHandle facilitates sharing. – phant0m Jun 27 '16 at 13:13
  • @phant0m CHandle doesn't provide a shared ownership. – Sergey Podobry Jun 29 '16 at 13:22
2

Take a look at boost 2: shared_ptr wraps resource handles

Nemanja Trifunovic
  • 24,346
  • 3
  • 50
  • 88
2

Here is my alternative, which is quite nice except you need to dereference always after .get() and requires a functor or lambda:

template<typename HandleType, typename Deleter>
std::shared_ptr<HandleType> make_shared_handle(HandleType _handle, Deleter _dx)
{
    return std::shared_ptr<HandleType>(new HandleType(_handle), _dx);
}

then:

auto closeHandleDeleter = [](HANDLE* h) {
    ::CloseHandle(*h);
    delete h;
};
std::shared_ptr<HANDLE> sp = make_shared_handle(a_HANDLE, closeHandleDeleter);
f_that_takes_handle(*sp.get());

what I like most about this is there is no extra work to have access to this:

std::weak_ptr<HANDLE> wp = sp; // Yes. This could make sense in some designs.

and of course, the helper function works with any handle type of the likes.

Andrey Starodubtsev
  • 5,139
  • 3
  • 32
  • 46
EwIck
  • 21
  • 1