A Template Wrapper for GlobalLock






4.38/5 (5 votes)
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:
// 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:
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:
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.:
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:
rec::CGlobalLock<DEVMODE> dm(hDevMode);
...
DEVMODE* pDevMode = dm.Get();
Thanks to the operator*
overload, you can also copy the pointer contents. For example:
rec::CGlobalLock<DEVMODE> dm(hDevMode);
...
DEVMODE dmCopy = *dm;
global_lock.h
Here is the code in full:
#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.