Is relying on a type of Windows descriptor that is a pointer?

Windows pens are sometimes annoying to remember to clean them (makes GDI with handles and brushes created is a great example). The RAII solution is great, but is it really that it makes one complete Rule of Five RAII class for each type of handle? Of course not! The best I see is one complete common RAII class with other classes, defining what to do when you need to clear the descriptor, as well as other aspects related to the descriptor.

For example, a very simple module class can be defined as follows (just an example):

struct Module { Module() : handle_{nullptr} {} Module(HMODULE hm) : handle_{hm, [](HMODULE h){FreeLibrary(h);}} {} operator HMODULE() const {return handle_.get();} private: Handle<HMODULE> handle_; }; 

It's all beautiful and dandy, not a destructor or anything else. Of course, however, the ability to write a Handle class does not need a destructor or anything else. Why not use existing RAII methods? One idea would be to use a smart pointer to void , but that would not work. Here, how descriptors are actually declared under normal circumstances:

 #define DECLARE_HANDLE(n) typedef struct n##__{int i;}*n DECLARE_HANDLE(HACCEL); DECLARE_HANDLE(HBITMAP); DECLARE_HANDLE(HBRUSH); ... 

It actually distinguishes the types of descriptors, which is good, but makes it impossible to use a smart pointer to void . What if instead, since descriptors are, by definition, pointers, a type can be extracted?

My question is whether to make the following safe assumption. It uses a desktop handle that must be closed. Disabling the distinction between generic and unique pointers (for example, FreeLibrary has its own semantics of reference counting), assumes that the descriptor is a pointer and makes a smart pointer that it points to everything in order, or I should not use smart pointers and do Handle realize the RAII aspects themselves?

 #include <memory> #include <type_traits> #include <utility> #include <windows.h> int main() { using underlying_type = std::common_type<decltype(*std::declval<HDESK>())>::type; std::shared_ptr<underlying_type> ptr{nullptr, [](HDESK desk){CloseDesktop(desk);}}; } 
+3
source share
4 answers

I believe that all Windows pointers are technically pointers to internal objects inside a part of the Windows kernel (or sometimes, perhaps to user objects highlighted by the kernel code, or some variations of this topic).

I am far from convinced that you should TREAT them as pointers. They are just pointers in a purely technical perspective. They are no longer "pointers" than the C style. "FILE *" is a pointer. I do not think that you would suggest using shared_ptr<FILE*> to work with closing files later.

Wrapping the descriptor into something that clears it later is a good idea anyway, but I don't think using smart pointer programs is the right solution. Using a templated system that knows how to close the handle would be ideal.

I suppose you will also need to deal with “I want to transfer this descriptor from here to another place” in a good way that works for all participants - for example, you have a function that somehow extracts resources and returns descriptors of these resources - you return an already wrapped object, and if so, how does the copy work?

What if you need to save a copy of the descriptor before using another (for example, save the current pen, then set a custom one, then restore)?

+2
source

One approach you can use is to use a template class:

 template<typename H, BOOL(WINAPI *Releaser)(H)> class Handle { private: H m_handle; public: Handle(H handle) : m_handle(handle) { } ~Handle() { (*Releaser)(m_handle); } }; typedef Handle<HANDLE,&::CloseHandle> RAIIHANDLE; typedef Handle<HMODULE,&::FreeLibrary> RAIIHMODULE; typedef Handle<HDESK,&::CloseDesktop> RAIIHDESKTOP; 

If there is a HANDLE that is not freed by a function like BOOL(WINAPI)(HANDLE) , you may have problems with this. If the release functions differ only in return type, you can add this as a template parameter and still use this solution.

+2
source

Technically, this should work fine in all modern versions of Windows, and it’s hard to find a real reason against this (it’s actually a very clever use of the existing standard library functions!), But I still don’t like the idea because:

  • Pens are pointers, not pointers. Handles are opaque types and should be considered compatible. Pens are pointers, and it is unlikely that pens will ever be anything else, but it is possible that this will change. In addition, descriptors may or may not have values ​​that are valid pointer values, and may or may not have different values ​​under 32 bits and 64 bits (say INVALID_HANDLE_VALUE ) or other side effects or behavior that you may not foresee now . Assuming that the descriptor has certain defined properties, it can work fine for decades, but it can (theoretically) mysteriously fail in some conditions that you have not thought about. Admittedly, this is very unlikely, but still it is not 100% pure.
  • Using a smart pointer in this way does not comply with the principle of least surprise . Because, hey, pens are not pointers. Having RAII built into a class named with an intuitive name ("Handle", "AutoHandle") will not force anyone to raise an eyebrow.
+1
source

I believe that both unique_ptr and shared_ptr allow you to provide a custom div. I believe that this is exactly what you need for proper control of the duration of the pens.

+1
source

All Articles