A Small Class for Saving and Restoring User's Printer Settings
A generic template class that wraps the GlobalAlloc API
Introduction
This is simple drop in C++ template class that makes it easy to save and restore a user's printer selection and settings. It does so by wrapping the HDEVMODE
and HDEVNAMES
structures. This class is a template class so it can be used to save and restore any memory block that has been allocated by the GlobalAlloc
API.
Background
I like to write simple easy to use tools that are not overly complicated or confusing to my users. One of the things I find most often is that users usually always print to the same printer using the same settings. Myself, I like to print most documents in black and white, and I find it annoying having to go through the print dialog options to reselect my printer and its options every time I print. So there must be a way to persist my settings so that everything stays the same unless I want to change it.
For printing, the way to accomplish this is to save and restore the HDEVNAMES
and HDEVMODE
structures that are used by windows and the printer drivers. Not always an easy task due to the variable amount of extra data that is often allocated onto the end of these structures.
The class presented here is my solution to this problem. My naming skills are not very imaginative so the name I came up with is MyGHND
. "My
" because I wrote it, and "GHND
" because that is the flag I used with the GlobalAlloc
API function when allocating memory within this code. One of the nice things about this class is that it is a generic template class so it can actually be used by any block of data that has been or needs to be allocated by the GlobalAlloc
API.
Using the Code
First, I am going to show you some code that uses this class. This first code block uses the MFC CPrintDialog
to show a Print Setup dialog. It gets initialized to either the previously saved settings or to the system default settings if there are no saved settings or the saved settings are somehow corrupted.
void CSchedDlg::OnBnClickedButtonPrintSetup()
{
// Print Setup Dialog
CPrintDialog PD(TRUE);
// SchedData.GetString() reads a text string from an XML file
// Here we show the MyGHND c'tor that takes a text string
MyGHND<DEVMODE> GDevMode(SchedData.GetString(L"DevMode"));
// Check if the DEVMODE structure is valid
if (GDevMode)
{
// make the hDevMode of the PRINTDLG structure point to our DEVMODE
PD.m_pd.hDevMode = GDevMode;
// Here we use the default c'tor
MyGHND<DEVNAMES> GDevNames;
// And then the assignment operator that takes a text string
GDevNames = SchedData.GetString(L"DevNames");
if (GDevNames)
{
PD.m_pd.hDevNames = GDevNames;
}
if (IDOK == PD.DoModal())
{
// User pressed OK so we save our settings
// Reinitialize our DEVMODE to the one in the PRINTDLG
GDevMode = PD.m_pd.hDevMode;
// SchedData.SetString saves a text string to a XML file
SchedData.SetString(L"DevMode", GDevMode);
GDevNames = PD.m_pd.hDevNames;
SchedData.SetString(L"DevNames", GDevNames);
}
}
This next code block shows how to create a printer device context using the saved data.
void CSchedDlg::OnBnClickedButtonPrint()
{
// Print a test page
// Load the DEVNAMES and DEVMODE structures from the XML file
MyGHND<DEVMODE> GDevMode(SchedData.GetString(L"DevMode"));
MyGHND<DEVNAMES> GDevNames(SchedData.GetString(L"DevNames"));
// Check if they are valid
if (GDevNames && GDevMode)
{
// Get the pointer to our DEVNAMES
LPDEVNAMES pDevNames = GDevNames;
// And extract the device names from it
std::wstring Driver = (LPWSTR)pDevNames + GDevNames->wDriverOffset;
std::wstring Device = (LPWSTR)pDevNames + GDevNames->wDeviceOffset;
std::wstring Output = (LPWSTR)pDevNames + GDevNames->wOutputOffset;
// Create the printer DC and print
CDC PrinterDC;
if (PrinterDC.CreateDCW(Driver.c_str(),
Device.c_str(),
Output.c_str(),
(LPDEVMODE)GDevMode))
{
PrinterDC.StartDocW(L"Test Document");
PrinterDC.StartPage();
PrinterDC.TextOutW(0, 0, L"Hello World");
PrinterDC.EndPage();
PrinterDC.EndDoc();
return;
}
}
MessageBox(L"First Select a Printer using \"Print Setup...\"",
L"Unable to Print",
MB_OK | MB_ICONINFORMATION);
return;
}
While this code used a SetString()
and a GetString()
function, any method used to set or get text strings from any type of storage can be used. It could be simple text files, the registry, or even a database of some sort. The text string used to save the data will only ever contain printable ASCII characters. It will not have any carriage return, new line, nor NULL
characters in it. The method you choose to use to save and read these text strings is beyond the scope of this article.
The MyGHND Class Members
Constructors
The MyGHND
class has five constructors:
template <typename TYPE>
class MyGHND
{
public:
// Default c'tor. Allocate sizeof(TYPE) bytes
MyGHND (bool AutoFree = true);
// Allocate specified number of bytes
MyGHND (size_t ByteCount, bool AutoFree = true);
// AutoFree is false because we did not allocate the memory, so do not free it.
MyGHND (HGLOBAL Object, bool AutoFree = false);
// copy c'tor
MyGHND (const MyGHND<TYPE>> &Other, bool AutoFree = true);
// Decode an encoded text string
MyGHND (std::tstring EncodedString, bool AutoFree = true);
Parameters
AutoFree
specifies if the GlobalFree
API function should be called on the HGLOBAL
handle when it is no longer needed. It defaults to true
for most of the constructors since this class allocates the memory it should be responsible for freeing it. The only time it defaults to false
is when an existing HGLOBAL
handle is passed in. Since this class did not allocate the memory, it should not delete it.
ByteCount
specifies the amount of memory, in bytes, that is to be allocated.
Object
is a previously allocated HGLOBAL
handle that is to be wrapped by this MyGHND
object.
Other
is a MyGHND
object that will be copied into this new MyGHND
object.
EncodedString
is a std::tstring
that was previously encoded by the ToString
member function. This string
will be decoded and the data entered into this MyGHND
object.
Four of the five constructors can throw a std::runtime_error
exception if the GlobalAlloc
function fails. The only constructor that does not throw an exception is the one that takes an HGLOBAL
handle as it does not call GlobalAlloc
.
Destructor
public: ~MyGHND ()
The destructor will call GlobalUnlock()
on the locked handle, and if AutoFree
(specified in the constructor) is set to true
, the destructor will call GlobalFree
to free the memory allocated.
Memory Management Functions
MyGHND
has three memory management functions.
public:
// calls GlobalAlloc(HGND, ByteCount) to allocate the memory
void Init(size_t ByteCount);
// calls GlobalFree() to free the memory
void Free();
// get the amount of memory allocated, in bytes
size_t Size();
Init()
will first free any memory that had been previously allocated before allocating the new memory. It can throw a std::runtime_error
if the GlobalAlloc
function fails.
Be careful when using Free()
as it will call GlobalFree
regardless of the state of the AutoFree flag (specified in the constructor). A safer way to clear the memory is to call Init(0)
with the ByteCount
set to zero.
Size()
simple returns the size of the memory block allocated, in bytes. The return value can be larger then the value called for in Init()
.
Type Casting Operators
There are five type casting operators in MyGHND
:
public:
// return a pointer to TYPE
operator TYPE * ();
// Allow access to TYPE's members
TYPE * operator -> ();
// get the HGLOBAL handle
operator HGLOBAL ()
// Get an encoded string in order to serialize this object
operator std::tstring ();
// check the validity of this object
operator bool ();
The bool()
operator returns false
if the underlying handle is NULL
or the size of the memory block is zero.
Assignment Operators
MyGHND
has five assignment operators. They all make a copy of the data, leaving the original data untouched.
public:
// copy the data
MyGHND<TYPE> & operator =(const TYPE *Pointer);
// copy the data
MyGHND<TYPE> & operator =(const TYPE &Reference);
// copy the data
MyGHND<TYPE> & operator =(const MyGHND<TYPE> &Other);
// copy the data
MyGHND<TYPE> & operator =(const HGLOBAL OtherGlobalHandle);
// Decode and assign the EncodedString
MyGHND<TYPE> & operator =(const std::tstring &EncodedString);
Serialization Functions
public:
// Encode the data to a text string and return the encoded string.
std::tstring ToString();
// Take the previously encoded string and initialize this object with it.
bool FromString(const std::tstring &EncodedString);
ToString
returns the encoded string
. The encoded string
will be empty if an error was encountered.
FromString
returns true
if successful or false
if the supplied string
was invalid or another error occurred.
History
- January 22, 2014 - Article published