Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simple Stack-Based Wrapper for Windows and XML Registries

0.00/5 (No votes)
9 Aug 2004 1  
Left to my own devices, 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).

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:

  1. 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.
  2. 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:
    1. First, the values must first be read from the registry to intermediate values;
    2. 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:
    1. First, the controls are read to intermediate values;
    2. 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:

  1. Create a RegKey instance using either RegKey::FromHKEY() or RegKey::FromFile().
  2. Select the portion of the registry you wish to work by accessing the appropriately named sub-key.
  3. Access RegValue instances to read or write the values of interest, also by name.
  4. 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:

// Step 1: Open the key. If the returned RegKey

// is invalid it must not be used

RegKey hkcu = RegKey::FromHKEY(HKEY_CURRENT_USER);
if (!hkcu.IsValid()) return;

// Step 2: Select the registry sub-section to work in

RegKey myKey = hkcu.Key("Software\My Company\My Product");

// Step 3: Manipulate keys

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"); 
    // returning a sub-RegValue by value, what happens to hkcu?

}

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:

// To get started, we open a root key on HKEY_CURRENT_USER

RegKey hkcu = RegKey::FromHKEY(HKEY_CURRENT_USER);
if (!hkcu.IsValid()) throw;

// We can get to sub-keys using Key-to-Key calling or using a delimted path

RegKey key1 = hkcu.Key("Software\My Company\My Product");  // delimited path

RegKey key2 = 
    hkcu.Key("Software").Key("My Company").Key("My  Product"); // key-to-key


ASSERT(key1 == key2);  // the keys are identical

 
// Similarly, addressing values can be done from the root or sub-key 

RegValue value1 = hkcu.Value("Software\My Company\My Product\Value 1");
// a single  delimited path from root


RegValue value2 = key1.Value("Value 1"); // from a  sub-key instance

ASSERT(value1 == value2); // either way, the value is the  same


// Up to this point the storage hasn't been modified and only the root key 

// is open.  The next line of code will cause the keys "Software", 

// "My Company", and "My Product" as well as the value "Value 1" be created.

// In addition, "Value 1" will be set to true (1).

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 RegKeys 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:

Class Relationship Diagram

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:

RegDemo Screenshot

And this is how the values of those controls are represented in the registry:

Screenshot of RegEdit

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:

  1. Layout the dialog in the resource editor.
  2. Create the MFC class to implement the dialog.
  3. Create "value" member variables for each of the controls using CString, int, bool, etc.
  4. Add two empty methods to the class:
    DoRegistryDataExchange(CDataExchange *pDX)
    DoControlsDataExchange(CDataExchange* pDX)
  5. Move the contents of the original DoDataExchange to DoControlsDataExchange.
  6. 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);
  7. 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);
    
        // use the Set* methods on RegDDX to move date between the registry
    
        // and your objects.  Depending on the value of pDX->m_bSaveAndValidate
    
        // the direction of information flow with be automatically determined.
    
        // regDX.SetString(, ); 
    
        // regDX.SetInt(, ); 
    
        //  
    
    }

    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here