Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A Template Wrapper for GlobalLock

4.38/5 (5 votes)
7 Dec 2006CPOL2 min read 1  
Safely manage handles (perfect for printer DEVMODE and DEVNAMES)

Introduction

I work with printers a lot. Many of my work projects deal primarily with printing, and, as a consequence, I have a lot of code that looks something like this:

C++
// Apply printer settings
DEVMODE* pDevMode = ::GlobalLock(hDevMode);
if (pDevMode != NULL)
{
 pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
 ...
}
::GlobalUnlock(hDevMode);

Sometimes it's a DEVMODE handle, other times a DEVNAMES, and occasionally it might be a handle used when dealing with the Clipboard. All of the time the actions are the same - lock the handle to return a pointer, do something with the memory, then unlock the handle - a perfect candidate for a helper class.

CGlobalLock

CGlobalLock is a template that allows you to lock access to a handle and return any type of pointer - DEVMODE, DEVNAMES, etc. The code ensures the handle is locked and unlocked automatically, and some useful operator overloads have been provided that allow you to use CGlobalLock where you would have had a pointer.

For example, the above code could be written as:

C++
rec::CGlobalLock<DEVMODE> dm(hDevMode);
dm->dmOrientation = DMORIENT_LANDSCAPE;

Simply ensure that the type of pointer you require is passed as the template argument, and let the class do the rest. This is simpler than the original code, and there is no chance of that pesky handle staying unlocked.

Similarly, you might want to lock a DEVNAMES handle:

C++
rec::CGlobalLock<DEVNAMES> dn(hDevNames);

The operator overloads mean that you can pass a CGlobalLock object to a function that expects a pointer to the specified pointer type, e.g.:

C++
rec::CGlobalLock<DEVMODE> dm(hDevMode);
...
HDC hDC = ::CreateDC(_T("Driver"), _T("Device"), _T("Output"), dm);

In this case, the CreateDC function takes a DEVMODE* as the final argument.

If you still need direct access to the underlying pointer, then use the Get method:

C++
rec::CGlobalLock<DEVMODE> dm(hDevMode);
...
DEVMODE* pDevMode = dm.Get();

Thanks to the operator* overload, you can also copy the pointer contents. For example:

C++
rec::CGlobalLock<DEVMODE> dm(hDevMode);
...
DEVMODE dmCopy = *dm;

global_lock.h

Here is the code in full:

C++
#pragma once

#include <assert.h>
#include <stdexcept>

namespace rec
{
 /// Template class used to lock a HANDLE and expose a pointer
 template <typename T>
 class CGlobalLock
 {
 private:
  HANDLE m_h;  ///< The handle to lock/unlock
  T* m_p;   ///< Pointer returned by ::GlobalLock
 public:
  /// Default constructor
  CGlobalLock() : m_h(NULL), m_p(NULL)
  {
  }

  /// Constructor from a HANDLE
  CGlobalLock(HANDLE h) : m_h(h)
  {
   Lock();
  }

  /// Destructor - Unlocks the handle
  ~CGlobalLock()
  {
   Unlock();
  }

  /// HANDLE assignment operator
  CGlobalLock& operator=(HANDLE h)
  {
   Unlock();
   m_h = h;
   Lock();
  }

  /// Return the pointer
  T* Get() const
  {
   assert(m_p != NULL);
   return m_p;
  }

  /// Operator overloads

  T* operator->() const
  {
   assert(m_p != NULL);
   return m_p;
  }
  
  T& operator*() const
  {
   assert(m_p != NULL);
   return *m_p;
  }
  
  operator T*() const
  {
   assert(m_p != NULL);
   return m_p;
  }
 private:
  /// Lock the handle
  void Lock()
  {
   if (m_h != NULL)
   {
    m_p = reinterpret_cast<T*>(::GlobalLock(m_h));
    // Did the lock succeed?
    if (m_p == NULL)
    {
     // The handle is probably invalid
     throw std::runtime_error("Failed to lock handle");
    }
   }
   else
   {
    m_p = NULL;
   }
  }

  /// Unlock the handle
  void Unlock()
  {
   if (m_h != NULL)
   {
    ::GlobalUnlock(m_h);
    m_h = NULL;
   }
  }
 };
}

Exceptions

If the GlobalLock function fails to return a pointer, then a std::runtime_error is thrown, as the chances are a bad handle has been passed, which I think is an exceptional circumstance. However, I also appreciate that exceptions are not everybody's cup of tea, so feel free to remove this code and leave m_p as NULL if you prefer. Note that each method that returns m_p will also assert if the pointer is NULL, which you are also free to change (you may want to use the MFC ASSERT macro instead, or ATLASSERT, etc.).

WTL

Note that if you are using the Windows Template Library, then there is a wrapper specifically for DEVMODE handles provided (called CDevModeT), but this class is more generic, and, as far as I can see, there is no WTL equivalent wrapper class for DEVNAMES handles.

Samples

As there isn't much to this template, I haven't provided any downloadable samples. However, I am happy to put something together if requested. If there are problems or questions, feel free to add a comment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)