Click here to Skip to main content
15,860,859 members
Articles / Multimedia / GDI
Article

boost 2: shared_ptr wraps resource handles

Rate me:
Please Sign up or sign in to vote.
4.96/5 (41 votes)
16 Nov 200415 min read 226.5K   1.3K   56   44
Using boost, we can write "almost perfect" wrappers for GDI and other resource handles, in a few lines of code.

Introduction

Windows resources, like GDI handles, are hard to manage correctly without creating leaks. This article shows how smart pointers can simplify this, and almost eliminate a major source of errors. It continues my previous article, Smart Pointers to boost your code, providing a practical example for using shared_ptr. (If you are not familiar with boost::shared_ptr, you should read it now!)

Update: While the first version was more or less like "concept art", I brushed up the sources a bit to make the more useful. I added a reset() function that sets the HandleRef to 0, brushed up the sources a bit and added a special implementation for HDC's. Also, I fixed some snippets here, and added a few things to the article.

Contents

  • Background will examine the problem in detail, and discuss the common solutions.
  • Smart Pointers to rescue will show how smart pointers can help here, with a simple example.
  • Developing a Solution will develop the idea into a set of template classes, that make reusing the idea flexible and allow adding new types. Sample code discusses the drop-in header provided.
  • Using the Solution - jump here if you want to know how to use it.
  • CDCRef for Device Contexts (new)
  • Discussion (new)

Background

Many resource handles encountered in Win32 programming do not fit an object-oriented environment very well. Here is a list of problems:

  1. How you acquire the handle determines if you have to call some cleanup function on it. If you use CreateFont, you have to call DeleteObject when you no longer need it. However, if you got the HFONT from a Window, you must not delete the font.
  2. There is no way to tell from a handle whether we should delete it or have to leave it alone.
  3. There are many handle types, with many different Delete/Release functions, which must be matched exactly.
  4. Handles have "pointer" semantics, i.e., copy constructor and assignment create only a new reference to the actual resource. This is desirable for performance reasons, however, this makes it complicated to use RAII in an object oriented environment.

If you have a function returning such a handle, you must at least specify if and when to release the handle. Things get much more complicated if the handle is a member of the class. Take a look at this innocent code snippet:

class CMessage
{
  protected:
    HFONT   m_font;
  public:

    ~CMessage();
  // ...
};

CMessage CreateMessage(CString const & msgText, 
  LPCTSTR fontName, int fontSize);

Code Snippet (1): Demonstrating the problem

Let me ask one question: Should the destructor of CMessage call DeleteObject(m_font)?

  • Yes? Then the return value of CreateMessage will give you a corrupted font handle to a font that does not exist anymore.
  • No? Who will delete the font then?

One way or another, the user of the class is concerned with managing your HFONT resource, or what he can do with your class is severely limited.

A multitude of solutions exist, some are simple, some not:

  • Prohibiting Copy Constructor, and Assignment operator (you can't have the CreateMessage function, then).
  • Keeping a "bool deleteTheFont" flag together with the font handle. (I worked with std::pair<HANDLE, bool> for a while, but this is still a pain.)
  • Count the references to the font. (E.g., the copy constructor would have to increment the reference count.)
  • Use some kind of internal reference counting. (This works for File, Event, Thread, and many other handles, using DuplicateHandle. However, this is another can of worms opening up. No such luck with GDI handles.)
  • Always copying the object (complicated, expensive, sometimes not possible).
  • Wrapping one of the solutions into a CFont class.

The nature of handles makes them prime candidates for a reference counting mechanism:

  • They act as "references to resources", but the resources themselves cannot/should not be copied.
  • The same resource is reused in many places, and must not be deleted as long as someone is using it.
  • It should, however, be released as soon as possible, to "play nice" with the system resources.
  • These resources discussed here don't reference each other, so circular references should virtually never appear.

Further, we would like to keep the possibility of "unmanaged" handles (i.e., it is not deleted automatically). This flag should be set when the smart pointer is created (close to where we acquire the resource, because this is the place where we know how to treat it).

See below: Why Not MFC?, why the MFC solution doesn't do it for me.

Smart Pointers to Rescue

As seen above, a reference counting smart pointer is ideal to handle Handle.

Why boost::shared_ptr is great for this:

  • shared_ptr does not put any requirements on the type of the resource. (We don't need to inherit HFONT from some CRefCountable class. Phew!)
  • shared_ptr allows a custom deleter, which can do the resource-specific cleanup. (DeleteObject for fonts, CloseRegKey for handles, etc.)
  • The custom deleter also allows to not delete the resource automatically.

Note: I mentioned custom deleters only briefly (if at all) in my previous article. See the boost documentation for more.

Let's look at the example for an HFONT:

// first we need a custom deleter for the font:
void delete_HFONT(HFONT * p) 
{ 
  _ASSERTE(p != NULL);   // boost says you don't need  this, 
                         // but lets play safe...
  DeleteObject(*pFont);  // delete the Windows FONT resource
  delete pFont;          // delete the object itself 
                         // (normally done by default deleter)
};

// typedef for our smart pointer:
typedef boost::shared_ptr<HFONT> CFontPtr;

// and a simple "Create" function:
CFontPtr CreateFontPtr(HFONT font, bool deleteOnRelease)
{
  if (deleteOnRelease) {
    // construct a new FontPtr. the custom deleter 
    // is specified as second argument
   return CFontPtr(new HFONT(font), delete_HFONT);  // (A)
  }
  else {
    // construct a new FontPtr with the default deleter:
    return CFontPtr(new HFONT(font));
  }
}

Code Snippet (2): The initial idea

The line (A) is the "magic" one: here we initialize the CFontPtr as usual, but specify that the object should be deleted using delete_HFONT when the last reference is gone.

Now, we can make heavy use of CFontPtr: we can use it as return value from functions. We can have it as class member, and the default copy constructor, assignment operator, and destructor do exactly what they should.

Do you remember the first rule for using smart pointers? Put the resource into a smart pointer as soon as you get it. This rule holds up here as well: because when we get the font handle, we know exactly if it should be deleted or not. The smart pointer will carry around this flag, and automatically "do what we want".

There are some problems even this solution cannot solve:

  • Someone could delete the font behind our back.
  • We could create the font pointer with deleteOnRelease = false, and then forget to delete it ourselves.

But such is life in C++, you are always free to shoot yourself in the foot you like most.

Some problems, however, can be solved better:

  • Checking for a "null" font is tedious: we have to check both the smart pointer, and the actual font:
    if (m_fontPtr && *m_fontPtr) // do we have a font?
  • We must dereference the smart pointer any time we want to access the actual object.
  • To have to write three entities (deleter, smart pointer typedef, Create function) for any other handle type we would like to support.

But this is the question of the next part.

Developing a complete solution

This paragraph shows how to turn the concept into a complete extensible solution. It might be helpful in understanding how template libraries evolve into the complex beasts they are.

The following goals were set:

  • Simple check for NULL resource
  • Automatic cast to the handle type
  • Extensible for other types
  • Minimize the amount of code to write for new types (only a typedef would be great)
  • Header-only definitions (no separate .cpp / .lib)

    This goal was actually set "on the run", because there is not much that can go into a .cpp, and header-only makes the library easier to reuse.

Encapsulation

First, we encapsulate the details of the smart pointer, solving the first two requirements:

class CFontRef
{
  protected:
    typedef boost::shared_ptr<HFONT> tBoostSP;
    tBoostSP m_ptr;

  public:
    explicit CFontRef(HFONT font, bool deleteOnRelease)
    {
      if (deleteOnRelease)
        m_ptr = tBoostSP(new HFONT(font), delete_HFONT);
      else
        m_ptr = tBoostSP(new HFONT(font));
    }

    operator HFONT() const { return m_ptr ? *m_ptr : NULL; }
};

Code Snippet (3): Moving to a specialized class

You might (or should) notice the following things:

  • The public interface is reduced to the bare minimum. (A "good thing".)
  • The CreateFontPtr function has become the constructor.
  • An automatic cast operator allows both the if (font) test, and using the class in a place where a HFONT is expected.
  • The deleter remains an associated function (not shown here).
  • The class was renamed from "Ptr" to "Ref", since it syntactically cast more like a reference than a pointer.
  • The constructor does not specify a default parameter. This was done since no value is the "obvious default". Further, it makes the construction explicit (so the explicit keyword isn't really needed).
  • I typedef'ed the boost pointer inside the class. It occurs in quite a few places, the typedef makes the code easier to read, but "outside" the class, no one really needs it.

Another note: Now would be a good time to make the simplification used below. However, I'd like to go the "safe & clean way" a bit further.

Second, we can make both the Handle type and the Deleter a template parameter. It's simple for the Handle, but some compilers can't handle a function as template argument. The standard solution is to turn the Deleter function into a functor - that is, a class that overloads operator().

template <typename HDL, typename DELETER>
class CHandleRefT
{
  protected:
    typedef boost::shared_ptr<HDL> tBoostSP;
    tBoostSP m_ptr; 

  public:
    CHandleRefT(HDL h, bool deleteOnRelease)
    {
      if (deleteOnRelease) m_ptr = tBoostSP(new HDL(h), DELETER());
      else m_ptr = tBoostSP(new HDL(h));
    } 
    operator HDL() const { return m_ptr ? *m_ptr : NULL; }
}; 

// the font deleter, turned into a functor:
struct CDeleterHFONT
{
    void operator()(HFONT * p) { DeleteObject(*p); delete p; }
}; 

// the typedef for our CFontRef:
typedef CHandleRefT<HFONT, CDeleterHFONT> CFontRef;

Code snippet (4): turning the class into a template

DeleteObject is used for many types, so we don't want to write our own deleter for each. However, we would like to keep everything strictly typed, so we make it a template again:

template <typename GDIHANDLE>
struct CDeleter_GDIObject
{
  void operator()(GDIHANDLE * p) { DeleteObject(*p); delete p; }
};

// the typedef now has a nested template:
typedef CHandleRef<HFONT, CDeleter_GDIObject<HFONT> > CFontRef;

Code snippet (5): a helper template

Specialized, more lightweight implementation

There are two things still bugging me:

  • The deleters still need to remember the "delete p" part.
  • I would like to avoid the heap copy of the resource handle if possible.

The first problem could be solved with another helper template, but that wouldturn the actual type of CFontRef into something like

boost::shared_ptr<HFONT, GenericDeleter< 
DeleteObjectDeleter<HFONT> > >
. I see no solution for the second in a generic approach.

However, both solve themselves when using some internal knowledge about Windows: all resource handles in Windows can be represented by a

void 
*
, and we can use a cast to get the strictly typed handle. This is so deeply rooted in the Win32 API (in fact, if you #undef the STRICT macro, most handle types are declared as
void 
*
), that we won't see a change till .NET takes over completely. Further, boost::shared_ptr can use void as template argument.

So, with the following code, we can go:

// a void deleter replaces the default deleter for "unmanaged" handles
struct CDeleter_Void
{
  void operator()(void *) {}
}

template <typename HDL, typename DELETER>
class CHandleRef
{
  protected:
    typedef boost::shared_ptr<void> tBoostSP;
    tBoostSP m_ptr;

  public:
    explicit CHandleRef(HDL h, bool deleteOnRelease)
    {
      if (deleteOnRelease)
        m_ptr = tBoostSP(h, DELETER());
      else
        m_ptr = tBoostSP(h, CDeleter_Void());
    }

    operator HDL() const { return (HDL) m_ptr.get(); }
};

struct CDeleter_GDIObject
{
  void operator()(void * p) { DeleteObject( (HGDIOBJ) p); }
};

typedef CHandleRef<HFONT, CDeleter_GDIObject> CFontRef;

Code snippet (6): the final code

Before, the smart pointer stored a pointer to our resource handle. Now, we store the resource handle directly in the smart pointer (represented as void *).

Using the solution

(I'll repeat a few things here for the impatient who skipped over all that boring explanation stuff)

CHandleRefT is a template class implementing a counted reference to a Windows resource handle. Usage rules are similar to a reference counted smart pointer. It is used to implement a variety of Windows Resource handles, like:

  • HIMAGELIST ==> CImageListRef
  • HMENU ==> CMenuRef
  • HANDLE ==> CHandleRef
  • HBITMAP ==> CBitmapRef
  • HBRUSH ==> CBrushRef
  • HPEN ==> CPenRef

Template parameters:

  • HDL: type of the resource handle (e.g., HFONT).
  • DELETER : a functor releasing the resources of Handle of type HDL (e.g., a functor calling DeleteObject). The handle is passed as void *.
    A custom deleter can be passed to the constructor. However, DELETER is not a template parameter of the class (which avoids template propagation)

Automatic vs. manual handles

When constructing a HandleRef from a raw handle, you pass a bDeleteOnRelease flag indicating if the handle should be released automatically when the last reference to it goes out of scope.

For an automatic handle, assign the raw handle to a HandleRef immediately after construction, specifying bDeleteOnRelease=true. Then, pass it around only as HandleRef. This guarantees the handle is deleted when it is no longer used.

For a manual handle, that you will delete yourself manually, or that must not be deleted, specify bDeleteOnRelease=false.

The idea here is that you can pass around, copy, store, or return a HandleRef, it remembers its deletion policy.

Guidelines

Utility functions that merely receive and use a resource handle (without storing it) may use the raw handle as argument. However, when the Handle is stored (e.g., as a class member) or used as a return value from a function, a HandleRef is recommended.

As an example, the CImageListRef class has the following members:

Constructor

CImageListRef(HIMAGELIST il, bool deleteOnRelease)

Initializes a new CImageListRef.

  • il [HIMAGELIST]: Image list to hold
  • deleteOnRelease [bool]: if true, the image List il is destroyed when the last reference to it (made through the CImageListRef instance) goes out of scope. The correct destroy function (ImageList_Destroy) is used.

operator HIMAGELIST

  • implicitly casts the CImageListRef to the contained HIMAGELIST.

reset()

Releases the image list.

To add support for a new type:

The type must be convertible to and from void *.

  • Write a Delete functor for the type:
    struct CDeleter_MyType { void operator()(void * p) { MyTypeRelease(p); }
  • use a typedef
    typedef CHandleRef<CMyType, CDeleter_MyType> CMyTypeRef;

Why not MFC?

MFC tried to solve the problem with its own wrappers. Unfortunately, they chose to wrap the first option from the "possible solutions" above: no copy functionality, but they pass around CFont *. This might have been a good design decision back then, but now, it is just a pain. Consider a function returning a CFont *. Now, there are two objects that need to be released correctly: the Windows HFONT, and the CFont C++ object. Should you:

  • Delete the CFont * when you are done with it, because it was allocated dynamically?
  • Not delete the CFont *, but use it only until some other class is destroyed (because the other class "holds" the font)?
  • Not delete the font, but use it only in the current message handler (because it's a temporary MFC object that is deleted in the next OnIdle call)?
  • Detach the HFONT from the CFont * before deleting it (because you have to get rid of the MFC object, but the Windows resource is still in use somewhere)?

When turning UI code into general routines and classes, I regularly stumble over all these four scenarios. It is not a pleasant experience, I can tell you - so I end up using raw resource handles instead, to have "only the normal problems".

Further, I like UI code not to be dependent on MFC, if possible. The best code IMO is a library that relies solely on the Win32 API interface, and does not put any requirements on the framework.

CDCRef for Device Contexts

The CDCRef class, referencing a HDC, deserves additional discussion.

Device Contexts are the most complicated resource I've come across: There are two cleanup functions: DeleteDC and ReleaseDC, and the latter needs an additional parameter (the HWND the DC was acquired from).

CDCRef is implemented as separate class, since I didn't want the complexity of HDC to "creep" into the Other HandleRef classes. The main difference is the constructor - instead of a flag, you directly pass the deleter:

CDCRef(hdc, CDCRef::Null()) // a CDCRef that doesn't get released automatically
CDCRef(hdc, CDCRef::Delete()) // a CDCRef to be released by DeleteDC
CDCRef(hdc, CDCRef::Release(hwnd))  // a CDCRef to be released by ReleaseDC

For ReleaseDC, the HWND is passed as parameter to the deleter. Again, we associated all information required for cleanup with the object when it is constructed, which is a design principle of HandleRef.

Further, CDCRef provides static member functions that wrap the Win32 API functions that acquire a HDC. For example, CDCRef::GetDC implements the GetDC Win32 API function, but returns a CDCRef.

Discussion

The solution provided here is not new. The idea of the article is to show that the solution becomes very simple when using an appropriate library (about 20 lines of actual code), and how such a solution would be developed from the initial idea to use shared_ptr.

Now, How good is the solution?

  • You need to know how Win32 Resources are managed
  • You need to know how smart pointers work
  • You have a slight overhead over plain Win32 code: a separately allocated object (8 Bytes) for each managed handle, and the cleanup call is through a function pointer.

The second is not an issue: smart pointers are such a fundamental technique that you shouldn't be caught without. The last might be an issue when handling lots of resources. However, such an application likely benefits most from automated resource management, and you can keep performance under control with using a raw handle (or a CHandleRefT<> const &) where appropriate.

The first actually reflects a design choice: CHandleRef does not isolate you from the underlying API, but makes it transparent and easier to use. (MFC, in contrast, isolates you very well in the default case, and fails misderably in all others). Additional advantage: CHandleRef is not only library independent, it also integrates easily with other libraries.

Is a "better" solution possible?

Definitely. You still can do things wrong: release an object while it is still selected into a device context, specify the wrong cleanup policy, or delete the handle while it is still in use. However, a completely safe solution would require to wrap the entire GDI API: all functions creating or using an GDI object.

CDCRef actually show the difference between an open wrapper (allowing access to the underlying API handles) and a close wrapper (denying the same access).

To make CDCRef construction "foolproof" the CDCRef constructor would have to be protected, so they can be constructed only through dedicated functions that initialize correctly. But this requires wrappers for all functions acquiring a DC. Further, to make CDCRef completely foolproof, one also would have to wrap all functions accepting a DC, and remove the operator HDC() from the class. The open wrapper leaves you some responsibility, but you have no problems if I forgot to provide a wrapper (or the original API is extended).

Conclusion and Thank You

Thank you all for the encouraging feedback! It is great to see the time to write the article was well spent.

For a more formal conclusion: We have seen that

  • Resource Handles, as many other objects, can have a variety of destruction policies, which are not "visible" from the handle itself
  • The deletion policy should be "attached" to the handle when the handle is acquired
  • reference counted smart pointers are perfect for wrapping resource handles
  • boost::shared_ptr provides the features to make an implementation simple
  • by using some platform-specific knowledge, we can make the solution more efficient, and less complex in terms of code, while keeping the original interface

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Klippel
Germany Germany
Peter is tired of being called "Mr. Chen", even so certain individuals insist on it. No, he's not chinese.

Peter has seen lots of boxes you youngsters wouldn't even accept as calculators. He is proud of having visited the insides of a 16 Bit Machine.

In his spare time he ponders new ways of turning groceries into biohazards, or tries to coax South American officials to add some stamps to his passport.

Beyond these trivialities Peter works for Klippel[^], a small german company that wants to make mankind happier by selling them novel loudspeaker measurement equipment.


Where are you from?[^]



Please, if you are using one of my articles for anything, just leave me a comment. Seeing that this stuff is actually useful to someone is what keeps me posting and updating them.
Should you happen to not like it, tell me, too

Comments and Discussions

 
PraiseThank you for sharing Pin
Guan Wang 20213-Jul-23 14:00
Guan Wang 20213-Jul-23 14:00 
QuestionBetter to make the deleter hold the handle Pin
Neil Morgenstern6-Jul-17 0:02
Neil Morgenstern6-Jul-17 0:02 
GeneralWTL classes to manage GDI Handles etc... Pin
Member 122908329-Oct-07 21:55
Member 122908329-Oct-07 21:55 
GeneralRe: WTL classes to manage GDI Handles etc... Pin
peterchen30-Oct-07 10:05
peterchen30-Oct-07 10:05 
GeneralHandles already use Reference counts Pin
Zac Howland17-May-06 8:26
Zac Howland17-May-06 8:26 
GeneralRe: Handles already use Reference counts Pin
peterchen17-May-06 9:47
peterchen17-May-06 9:47 
GeneralRe: Handles already use Reference counts Pin
Zac Howland18-May-06 3:43
Zac Howland18-May-06 3:43 
GeneralRe: Handles already use Reference counts Pin
peterchen19-May-06 22:23
peterchen19-May-06 22:23 
Generalunderstanding how template libraries evolve into the complex beasts they are Pin
Roland Pibinger26-Oct-04 2:37
Roland Pibinger26-Oct-04 2:37 
GeneralRe: understanding how template libraries evolve into the complex beasts they are Pin
peterchen26-Oct-04 23:14
peterchen26-Oct-04 23:14 
GeneralSelectObject Pin
Stewart Tootill14-Oct-04 21:23
Stewart Tootill14-Oct-04 21:23 
GeneralRe: SelectObject Pin
peterchen15-Oct-04 4:01
peterchen15-Oct-04 4:01 
GeneralRe: SelectObject Pin
Stewart Tootill15-Oct-04 5:01
Stewart Tootill15-Oct-04 5:01 
GeneralRe: SelectObject Pin
peterchen15-Oct-04 12:31
peterchen15-Oct-04 12:31 
GeneralRe: SelectObject Pin
peterchen17-Nov-04 2:42
peterchen17-Nov-04 2:42 
GeneralTemplate Specialisation Pin
Stewart Tootill14-Oct-04 21:17
Stewart Tootill14-Oct-04 21:17 
GeneralRe: Template Specialisation Pin
peterchen15-Oct-04 3:47
peterchen15-Oct-04 3:47 
GeneralRe: Template Specialisation Pin
Stewart Tootill15-Oct-04 5:27
Stewart Tootill15-Oct-04 5:27 
GeneralWhy yes MFC Pin
TW6-Oct-04 15:55
TW6-Oct-04 15:55 
CAutoPtr<cfont> newFont(new CFont);
...

// in copy constructor or assignment operator etc
left.newFont = right.newFont; // Exchange ownership, whoever owns it ultimately, the GDI object will be deleted in destructor

You can certainly extend MFC to suit your case, but never the less MFC has often provide a good ground to start with.

You may also use specialize template instead of functor, to have compiler automatically choose which API to call, when closing a handle.

Good day.
GeneralRe: Why yes MFC Pin
peterchen6-Oct-04 19:58
peterchen6-Oct-04 19:58 
GeneralRe: Why yes MFC Pin
TW7-Oct-04 16:21
TW7-Oct-04 16:21 
GeneralRe: Why yes MFC Pin
peterchen7-Oct-04 20:35
peterchen7-Oct-04 20:35 
GeneralRe: Why yes MFC Pin
TW8-Oct-04 0:20
TW8-Oct-04 0:20 
GeneralRe: Why yes MFC Pin
gghelli8-Oct-04 2:13
gghelli8-Oct-04 2:13 
GeneralRe: Why yes MFC Pin
TW10-Oct-04 16:05
TW10-Oct-04 16:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.