Motivation
I find using the Windows registry prone to errors. I tend to leak handles, leave keys open too long, close and re-open keys too often, and generally make a mess of the whole thing. And frankly, so do most of you (no insult intended). In addition to that, I like to give users the option of avoiding the registry altogether and storing configuration information in XML. This is particularly useful when configuration information is shared among several machines (e.g., on a shared drive or accessed via URL), and when code runs on registry-less platforms.
For my own benefit, I created a simple (perhaps even simplistic) wrapper for the registry and forced myself to use it. It wasn't easy. But, in the end, I've found these little classes to be gems, and thought it appropriate to share them here.
Please note that I originally created this utility to wrap only the Windows registry, and added XML support later. So, where the term registry appears in this article, keep in mind that I'm talking about the concept of the registry, which may be stored in the actual Windows registry or some other storage (XML or a database, for example).
The Basics: RegKey and RegValue, and RegDDX
There are two basic classes you'll see the most of when using this code:
RegKey
is the entry-point class. To instantiate a RegKey
, call the static member function that corresponds to the desired storage (RegKey::FromHKEY()
for Windows registry, and RegKey::FromFile()
for XML). RegKey
is a container of other RegKey
and RegValue
objects, which can be accessed using its Key()
and Value()
methods.
RegValue
is the value-holding class, and exposes interfaces for getting and setting the value. If the internal type is different from the requested type, RegValue
will attempt to make a reasonable conversion (e.g., int
to string via itoa
). RegValue
objects are accessible only through a valid RegKey
.
In addition to the two basic classes above, the file RegistryDX.h contains two classes to help in writing dialogs: RegDDX
and RegDDXMethodCall
. At runtime, these classes work to synchronize values in controls with values in the registry via a two-step process:
- When the dialog starts:
- First, the values must first be read from the registry to intermediate values;
- Second, the intermediate values can be applied to the controls using DDX.
- When the dialog is closed in the affirmative (user hits "OK"), the operations are done in reverse:
- First, the controls are read to intermediate values;
- Second, the intermediate values are sent to the registry.
The old C++ trick of using stack construction/destruction reversal is used by RegDDXMethodCall
to swap the call order based on the value of CDataExchange::m_bSaveAndValidate
.
Data validation should happen as data moves to and from the controls rather than to and from the registry. Although, unexpected values coming from the registry (e.g., when a user hand-edits something) should be handled gracefully too.
Details on Usage
RegKey
hides the complexities of creating, opening, and closing keys, and manages Win32 handles where necessary. Similarly, RegValue
wraps the concept of a registry value and manages creating, writing, and reading values. For simplicity, both classes are designed to be used on the stack. Stack-based objects make memory management less of a concern, and I generally find it easier to type '.
' than '->
'. Yes, I'm that lazy.
To use the wrappers, you need to do three (or four) things:
- Create a
RegKey
instance using either RegKey::FromHKEY()
or RegKey::FromFile()
.
- Select the portion of the registry you wish to work by accessing the appropriately named sub-key.
- Access
RegValue
instances to read or write the values of interest, also by name.
- If the registry is stored in an XML file, you need to call
Commit()
to write changes. When using the Windows registry, this step isn't required since all changes are made immediately.
Or, in the form of code:
RegKey hkcu = RegKey::FromHKEY(HKEY_CURRENT_USER);
if (!hkcu.IsValid()) return;
RegKey myKey = hkcu.Key("Software\My Company\My Product");
myKey.Value("Foo").Set(true);
Easy, eh?
Now, let's talk about when keys are closed, which can be a bit complex. A key cannot be closed until all of the keys below it have been closed. A key must remain open until all downstream handles are available to be closed, and then must be closed from the "bottom" up. To support this, the implementation behind RegKey
and RegValue
is implemented as a doubly-linked reference-counted tree (details below).
Consider what happens in this example, where a sub-key (or value) is returned by value from one function to another. This example illustrates why it's important to keep parent keys alive until all references to child keys have been dropped:
void StartHere()
{
RegValue value = MyGetRegValueFunction("Software\My Company\My Product");
value.Set(true);
}
RegValue MyGetRegValueFunction(const char* path)
{
RegKey hkcu= RegKey::FromHKEY(HKEY_CURRENT_USER);
if (!hkcu.IsValid()) throw;
return hkcu.Key(path).Value("Foo");
}
If we closed the root storage in MyGetRegValueFunction
when hkcu
went out of scope, then StartHere
wouldn't be able to use the returned sub-key. When StartHere
returns and value destructs, all references to the storage root (hkcu
) are gone, and it can safely be closed to avoid resource leaks. It's simple on the surface, but complex behind the scenes.
Handling Handles
Storage is the word I use to describe the place registry keys and values are persisted. This may be the Windows registry, an XML file, or maybe someday an ODBC connection. The call to RegKey::From*()
will always open or create the requested storage root (root key, file, or connection), or fail if it can't. Failure is indicated by returning an invalid RegKey
(test with key.IsValid()
). Using an invalid RegKey
will generally result in an exception.
The method RegKey::Key()
will never open or create a key storage. Rather, the method simply provides an interface to a key at the specified path. If it becomes necessary that the key exists (e.g., when a value within the key is "set"), if the storage does not exist, it will automatically be created.
Likewise, RegKey::Value()
will not create or open a value, but provides an interface that can be used to manipulate a key at that location. Only when storage for the key is needed will it actually be created.
This is weird, I know. But the main idea with this code is to spare developers worry over if a key or value exists and when to open and close it. In my view, the system should automatically handle these actions when and if required. When you decide to set the value of the key, it needs to be there. But, when you get the value, that should just fall back on the default. It was tempting to add a protocol to RegKey
and RegValue
that tested for the existence of the registry key in storage, but I managed to resist. Basically, I never came up with a legitimate case where I needed to know.
The overloaded method RegValue::Get()
will attempt to open the value and the chain of keys that lead to it at the path specified, but will not create one if it doesn't exist. If the key does exist, the value is read and returned. I like this approach because it doesn't require adding tons of defaulted keys to the registry. Since I have to supply an object to Get()
by reference, I'm already deciding on a default for the value so it isn't necessary to create the key.
The overloaded methods RegValue::Set()
, on the other hand, need a real entry to do their job. Consequently, Set
will attempt to open (or create if necessary) the chain of keys leading to the value, and the value itself. This is the main way that keys and values are created using these wrappers -- by setting the content.
It might be more clear to look at some code, so here is another example:
RegKey hkcu = RegKey::FromHKEY(HKEY_CURRENT_USER);
if (!hkcu.IsValid()) throw;
RegKey key1 = hkcu.Key("Software\My Company\My Product");
RegKey key2 =
hkcu.Key("Software").Key("My Company").Key("My Product");
ASSERT(key1 == key2);
RegValue value1 = hkcu.Value("Software\My Company\My Product\Value 1");
RegValue value2 = key1.Value("Value 1");
ASSERT(value1 == value2);
value1.Set(true);
It's convenient to specify a path in slash-separated format using a resource string (IDS_
...) to get to a key of interest. Using recursive calls to the Key
and Value
methods of RegKey
can be convenient if you want to write routines that take RegKey
s as parameters and make "cookie cutter" manipulations on the indicated registry section.
That's pretty-much it for using RegKey
and RegValue
. They are simple classes that simplify using the registry (at least for me). The sample application shows some more examples of how they can be used. Now, on to the guts...
Design and Implementation
RegKey
and RegValue
follow the pImpl and Handle patterns. Looking into Registry.h, you won't get details of the implementation beyond naked declarations of KeyImpl
and ValueImpl
. This reduces the apparent complexity of the classes, and isolates clients from implementation changes. Additionally, RegKey
and RegValue
contain only one pointer (m_pImpl
) and have no virtual functions, making them very lightweight.
The implementation classes doing the real work can be found in RegistryImpl.h, RegistryHKEY.cpp, and RegistryXML.cpp. The relationship between a RegKey
and KeyImpl
(the implementation of a key and its storage) is many to one. That is, there may be many RegKey
objects that reference the same implementation instance. RegValue
and RegValueImpl
follow the same pattern, and are illustrated in Figure 1, below:
Figure 1: The relationship between interface objects (above the dashed line) and implementation objects (below the dashed line).
Using RegDDX with Dialogs
RegDemo is a small dialog-based application that demonstrates the basic usage of RegKey
and RegValue
, as well as how to very quickly and simply implement a dialog that persists settings in a registry. The example dialog is made up of three simple controls:
And this is how the values of those controls are represented in the registry:
Oops, I took the screenshots at different times, so the values don't match. Let's just pretend I haven't hit "OK" yet in the picture of the dialog...
The very simple steps to registry-enable a dialog are as follows. If you already have a dialog implemented, skip steps 1 and 2, and maybe step 3:
- Layout the dialog in the resource editor.
- Create the MFC class to implement the dialog.
- Create "value" member variables for each of the controls using
CString
, int
, bool
, etc.
- Add two empty methods to the class:
DoRegistryDataExchange(CDataExchange *pDX)
DoControlsDataExchange(CDataExchange* pDX)
- Move the contents of the original
DoDataExchange
to DoControlsDataExchange
.
DoDataExchange
now gets the following two-line implementation, "YourDlgClass" replaced by the name of your dialog implementation class: RegDDXMethodCall<YourDlgClass> ddx1(pDX, this, DoRegistryDataExchange);
RegDDXMethodCall<YourDlgClass> ddx2(pDX, this, DoControlsDataExchange);
- Implement the method
DoRegistryDataExchange
as required to move data to/from the registry. The following boiler-plate code might be useful: RegKey base = RegKey::FromHKEY(HKEY_CURRENT_USER);
if (base.IsValid()) {
RegKey base = hkcu.Key(CString((LPCTSTR)IDS_REG_ROOT));
RegDDX regDX(pDX, base);
}
After doing the above, the specified values from your dialog should automatically be saved when OK'd, discarded when Cancel'd, and restored when re-invoked.
Clearly, these classes make implementing Settings dialogs a far sight easier than relying on the facilities of MFC and Win32 alone. In addition, you get a level of abstraction from the registry that allows you to switch between using the Windows registry or XML file as required -- or perhaps use both.
Conclusion
At one point, I ran this code through BoundsChecker and it came up clean. Since then I have modified it, and it may be leaking. If so, please add a comment below describing the leak and I'll take care of it.
History
- Updated August 7, 2004 with support for XML storage in addition to Windows registry storage. The signature of the API
RegKey::FromHKEY()
has changed to omit the bool
by reference; use IsValid()
instead.
- Original version posted February 3, 2003.