Download source files - 29 Kb
Download sample application - 113 Kb
The problem
I personally find the Win32 Registry API to be
overly complex for performing simple operations. The Win32 Registry API is
very "rich". You can do a lot with it, but most of the time you
just want to read or write a value. The error checking involved in doing even
simple registry operations is enough to hide the meaning of the code. Most
of the time you either want the operation to succeed or fail as a whole, you're
not interested in handling errors at each stage and performing recovery operations...
A simple Registry
API wrapper class
I've seen many Win32 Registry API wrapper classes
but all of them failed to actually make it easier to use the registry than
the API. Most have been simple wrappers which add no value (apart from saving
you the bother or having to pass the HKEY to each function...).
CRegistryKey
is essentially just a very simple wrapper around a HKEY.
It adds value by converting errors to exceptions, defaulting most of the rarely
used parameters with sensible values and providing STL style iterators for
sub keys and values. All of the Win32 Registry API is present as member functions,
but if you want to check for errors without using exceptions there's a conversion
operator that will allow access to the underlying HKEY.
CRegistryKey
was designed to make code like this as easy as possible...
try
{
CRegistryKey key = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE"));
for (CRegistryKey::SubkeyIterator it = key.BeginSubkeyIteraton();
it != key.EndSubkeyIteraton();
++it)
{
tcout << it.GetName() << endl;
CRegistryKey subKey = it.OpenKey();
for (CRegistryKey::ValueIterator val = subKey.BeginValueIteration();
val != subKey.EndValueIteration();
++val)
{
tcout << " "
<< val.name() << T(" ") << val.valueAsString()
<< endl;
}
}
}
catch (CRegistryKey::Exception &e)
{
e.MessageBox();
}
Remove a few inconsistencies
and tidy things up
RegDeleteKey
behaves differently on Win95 and NT. CRegistryKey::DeleteKey(
)
acts consistently on both platforms, never removing sub keys, whilst CRegistryKey::DeleteKeyAndSubkeys(
)
always removes sub keys.
In addition, CRegistryKey::OpenKey(
) only opens a key and does not create
one, use CRegistryKey::CreateKey(
) to create one and CRegistryKey::CreateOrOpenKey(
)
if you don't care.
Why complicate
matters with reference counting?
CRegistryKey
is slightly more complex than just a wrapper. It's actually
implemented using the "handle-body idiom", the CRegistryKey
object is a handle only the underlying HKEY which is reference counted. This
is so that it's easy to pass CRegistryKey
's by value. Since CRegistryKey
owns an open HKEY, and since that key is closed when the CRegistryKey
object goes out of scope we have to be careful when copying them around. It's
not enough to allow the default copy constructor and assignment operators
to be used. Allowing that would mean that the underlying HKEY could be closed
more than once, as in the example below...
void DoStuffWithKey(CRegistryKey key1)
{
}
try
{
CRegistryKey key1 = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE"));
DoStuffWithKey(key1);
}
catch (CRegistryKey::Exception &e)
{
e.MessageBox();
}
An alternative could be to duplicate the HKEY
using DuplicateHandle(
)
in the copy constructor and assignment operator, but that's messy, and unnecessary,
and besides it will fail if the HKEY
is a key on a remote machine.
A better, if slightly more complex, solution is to hold a single representation
of the underlying HKEY
and only close it when the last CRegistryKey
that references it is destroyed.
To achieve this, we store a counter with the HKEY
and increment the
counter every time we copy a CRegistryKey
that refers to the HKEY
,
and decrement the counter when we destroy such a CRegistryKey
. With
this in place there is only ever one copy of the open HKEY
no matter
how many additional CRegistryKey
objects are created from the initial
one. What's more, as a user of CRegistryKey
we never see any of this
complexity - it just works.
Iterators and templates
The code that makes it easy to navigate sub keys or values associated with
a CRegistryKey
is structured in a way that is similar to STL iterators.
This makes it easy and intuitive to use, just ++ your way along the list of
keys or values. When I was writing these iterators, I realised how much code
was similar between the sub key iterator and the value iterator. At first
I tried to factor this common code into a common base class. This didn't really
work, a lot of the common stuff was in assignment or equality operations which
didn't work well as base class members. Next I tried a template base class
which took the derived class with the specialist knowledge as its template
parameter. Whilst this was better (the assignment and equality ops could be
based on the template parameter rather than the base class) it still wasn't
ideal as constructor of the iterator has to "prime the pump" by
advancing to the first available item. This involved calling the Advance()
function which in turn needed derived class functionality in the form of a
virtual function call to GetItem()
. Of course, a virtual function call
from a base class constructor is not going to work in this instance.
The end result was an iterator template which
takes a template parameter to its base class. The base class implements the
specialist knowledge and is available at any point in the life of the template
derived class. To make life easier for myself I write a very simple abstract
interface class which represented the interface required by the template class
for it to work. This wasn't required, the template would have worked with
any class that implemented the required interface, not just those derived
from the abstract interface class, but it made the requirements easier to
see. Base classes are quite at liberty to add any additional functionality
that they require of the resulting iterator - for example, they might add
access functions for parts of the item being iterated over. While I was implementing
the iterator requirements base class I realised that all of the handling of
the underlying registry key, which was common between both implementations,
would need to go here, rather than in the template class. Only if the code
were in a base class of the implementation could the implementation use it...
The resulting classes involved look something
like this...

At the end of the day, for the sake of removing some duplicate code, the iterators
are more complex. This complexity is purely an implementation issue, it doesn't
leak through into the iterator interface and affect users. Even so, I'd probably
do them differently next time...
Putting it all
together
Now that we have examined all of the pieces our
simple Win32 Registry API wrapper class has turned out to be a collaboration
of quite a few classes, as can be seen from the diagram below:

Only CRegistryKey
itself is declared at the namespace level, all other
classes are nested within CRegistryKey
. This allows for class names
to be less specific as they are already scoped within CRegistryKey
- for example we can safely call the value class "Value" as the
only way to access it is as " CRegistryKey::Value
".
Known limitations
At present CRegistryKey
doesn't provide wrappers for the following
Win32 Registry API calls, this shouldn't cause a problem since you can always
access the underlying HKEY
to make these calls.
-
RegQueryMultipleValues(
)
-
RegQueryValueEx(
) and RegSetValueEx(
) for REG_LINK
,
REG_MULTI_SZ
and REG_RESOURCE_LIST
value types.
-
RegQueryInfoKey(
) - Most of the information is not required due
to other wrappers.
Since a CRegistryKey
represents an open registry key there's no way
to close the key whilst the key is in scope. This hasn't proved to be a problem
for me, but if it does you can always assign one of the standard key handle
values to your CRegistryKey
which will cause the open key to be closed
and the standard key to be open - since the standard key handle values appear
to be treated differently to normal keys (you don't need to open or close
them) this has the desired effect.
try
{
CRegistryKey key = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE"));
key = HKEY_LOCAL_MACHINE;
}
catch (CRegistryKey::Exception &e)
{
e.MessageBox();
}
I personally tend to scope the key so that it remains open whilst it is in
scope and then is automatically closed when it goes out of scope... Don't
pass the CRegistryKey
to the RegCloseKey(
) API as this will
cause the key to be closed twice, once by the RegCloseKey(
) call and
once when the CRegistryKey
goes out of scope or has another key assigned
to it.
See the article on Len's homepage for the latest updates.