Registry API Wrapper






4.92/5 (9 votes)
The Win32 Registry API is far too complex for simple tasks, and all the error checking gets in the way of the real work...
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 each sub key of this key... for (CRegistryKey::SubkeyIterator it = key.BeginSubkeyIteraton(); it != key.EndSubkeyIteraton(); ++it) { // Dump out the key's name tcout << it.GetName() << endl; // Then display each value 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) { // blah blah blah, do clever stuff with a key... // key1 is closed here when it goes out of scope! } try { CRegistryKey key1 = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE")); // call a function and pass the key by value... DoStuffWithKey(key1); // key1 is closed here - for the second time!!! } 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(
) andRegSetValueEx(
) forREG_LINK
,REG_MULTI_SZ
andREG_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")); // Do some key stuff... // Force the key closed... key = HKEY_LOCAL_MACHINE; // Do other stuff in this scope... } 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.