Windows registry interface C++ class as proper C++ wrapper

Written in 2012 year
An article is about simple Windows registry access wrapper for C++ programs
Download source code

Theoretical requirements and considerations

There are too many C++ classes for access a Windows registry in various C++ libraries, frameworks and so on. They all have distinct properties from a user point of view. And user may have completely different levels of comfort during working with them.

So, what interface should generic C++ class have for comfortable using him in programs? Let’s try to formalize basic properties of “comfort” class:

  1. It should be a value-class. User should not be locked to pass it as a parameter to functions, return it from them, and freely copy it and so on.
  2. It should manage its underlying raw handles, pointers to system resources and do all reference-counting and cleanup. User should not have a chance to leave some handle opened by mistake.
  3. It should use standard types, classes, containers for carrying data as much as possible.
  4. It should signal about errors standard way – using exceptions mechanism. A class should guarantee at least base exception safety. Preferable is to guarantee strong exception safety of course. (http://en.wikipedia.org/wiki/Exception_handling).

Next properties are added to classes, which are representing some external systems:

  1. User may have an access to underlying handle, but such access should be explicitly specified in the user’s code. To tell the truth an access to underlying handle is an indicator of a non-fullness of designed interface. So, the presence in the code of a class something like this:
operator HFONT()
{
    return m_hFont;
}

is a way to easily violate class invariants using indirect conversions by mistake.

  1. There are various cases when user may get a raw handle from some boundary’s API and become an owner for it. Or he (she) needs to send a handle to such API and forget about it. For such operations an explicit methods called, for example, "attach" and "detach" should exist in a code.
  2. Interface may have a methods accepted raw handles on input. Default behavior is to not become and owner of such handles, not release them and so on, without specifying some explicit flags, in case if an interface has them.
  3. Underlying system may carry some data using particular system’s data types. A class should have strong mapping from internal data types to C++ program data types. User should not have possibility to store underlying type to inappropriate C++ type implicitly.
  4. If an underlying system enables to enumerate some items, this aspect should be translated to iterators being usual for C++ world. Thus user gets an ability to use system’s items indirectly in various generic algorithms.

Additionally there are some thread-safety aspects of course. They will be dropped here for simplicity. The class, discussed in the article as a sample, is not thread-safe.

The class public interface description

The set of Windows registry access functions has two versions as many others: ascii and unicode (to be more precise, multibyte and wide char). So, there are a few approaches, how to utilize them. Somebody can use, for example, only ascii subset for simplicity. On the other hand a full set of functions may be wrapped by a class. In this particular case a C++ standard library approach has been selected. The class, called "basic_reg_key", is written as a template parameterized by a character type like many STL containers. So, particular template instantiation utilizes particular subset.

The class widely uses text strings in data-carrying and to specify the keys’ names. What particular type user applies to carry a text? Most generic are C-like null-terminated strings and std::basic_string objects from the standard C++ library. Due to absence of evident selection, a text strings in the class are represented by template parameters and the class uses simple template helpers to get an actual string’s data. So, user may specify "const CharT*" type or "std::basic_string<CharT>" type as input, assumed that the same "CharT" is used as parameter for the "basic_reg_key".

What types of exceptions should be used for particular error types? One kind of errors is inappropriate using of an object. Such errors don’t depend on external conditions and are stable meaning that they reproducible. They are represented by standard type "std::logic_error". For example, each time we try to use non-opened key, "std::logic_error" will be raised. Second kind is runtime errors. They appear by chance and depend on external conditions. It’s obvious to represent them with "std::runtime_error". Sometimes such errors can carry additional information from underlying system if the last one is an actual source for them. In this case a descendant "win_error" is raised.

The "basic_reg_key" class provides strong exception guarantee. Below is the description of its interface.

Declaration:

template <class CharT> class basic_reg_key;

The class is designed to be parameterized by "char" or "wchar_t" type.

Constructors:

basic_reg_key();

template <class Str>
basic_reg_key(HKEY parent_key,
    Str subkey_name, REGSAM access=KEY_ALL_ACCESS);

template <class AnotherCharT, class Str>
basic_reg_key(const basic_reg_key<AnotherCharT> &r,
    Str subkey_name, REGSAM access=KEY_ALL_ACCESS);

First method constructs an unbounded object which doesn’t represent any registry key. Second method opens a subkey under specified raw key with specified name and access rights. Third one does the same thing using another key class as a parent. In case of an error second and third methods raise an exception of "win_error" type.

Copy constructors:

basic_reg_key(const basic_reg_key &r);

template <class AnotherCharT>
basic_reg_key(const basic_reg_key<AnotherCharT> &r);

Both methods copy system's handles using WinAPI "DuplicateHandle" function. The duplicated handle has the same access rights as the original one. The methods raise a "win_error" in case of any error.

Usual openers:

template <class Str>
void open(HKEY parent_key, Str subkey_name, REGSAM access=KEY_ALL_ACCESS);

template <class AnotherCharT, class Str>
inline void open(const basic_reg_key<AnotherCharT> &r,
    Str subkey_name, REGSAM access=KEY_ALL_ACCESS);

The methods work exactly the same way, as constructors. User may call them for unbounded object only. Else the "std::logic_error" will be raised.

Closer, destructor:

void close() throw();

~basic_reg_key();

Destructor calls the "close" method automatically. The last one can be called for the object in any state.

void swap(basic_reg_key &r) throw();

basic_reg_key& operator=(const basic_reg_key &r);

template <class AnotherCharT>
basic_reg_key& operator=(const basic_reg_key<AnotherCharT> &r);

The "swap" method swaps the system's handle and the state variable with specified object. The copy operators use the "swap" method to make a copy with strong exception guarantee.

Getters:

template <class Str> const DWORD get_DWORD(
    Str name, DWORD def, bool ignore_absence=false,
    bool ignore_wrong_type=false) const;

template <class Str> const std::basic_string<CharT> get_REGSZ_or_EXPAND(
    Str name, const CharT* def=NULL, bool ignore_wrong_type=false,
    bool dont_expand=false) const;

template <class Str> const std::wstring get_LINK(
    Str name, const wchar_t* def=NULL,
    bool ignore_wrong_type=false) const;

template <class Str> multisz_ptr_t get_MULTISZ(
    Str name, bool ignore_absence=false,
    bool ignore_wrong_type=false) const;

template <class Str> binary_ptr_t get_BINARY(
    Str name, bool ignore_absence=false,
    bool ignore_wrong_type=false) const;

It is assumed that a number or a single string is a small amount of data and it can be returned by value. In contrast a multi-string or a binary data can be relatively big, so these types are returned using smart pointers. All these methods may raise "win_error". They may raise "type_error" exception in case where an actual data of a registry value have another type. User can ignore this aspect using the flag "ignore_wrong_type". If user specifies default value or the "ignore_absence" flag, these methods return default or empty value in case where an actual data is absent in the registry.

Setters:

template <class Str> void set_DWORD(Str name, DWORD value);

template <class Str1, class Str2> void set_REGSZ_or_EXPAND(
    Str1 name, Str2 value, bool is_regsz=true);

template <class Str1, class Str2> void set_LINK(Str1 name, Str2 value);

template <class Str, class It> void set_MULTISZ(
    Str name, It begin, It end);

template <class Str, class Ctr> inline void set_MULTISZ(
    Str name, const Ctr &c);

template <class Str> void set_BINARY(Str name,
    const BYTE* begin, const BYTE* end);

template <class Str> inline void set_BINARY(Str name,
    const std::vector<BYTE>& buffer);

When multiply strings should be stored in the MULISZ registry value, they can be specified as a generic container or a pair of generic iterators. For storing binary data user should specify two raw pointers to begin and past-end of a binary buffer or a std::vector<BYTE> object which is safer.

template <class Str> basic_reg_key& delete_value(Str name, bool ignore_absence=false);

template <class Str> basic_reg_key& delete_subkey(Str name, bool ignore_absence=false);

First method deletes the value specified by name. Second one recursively deletes specified subkey. In case if value or subkey absent and the flag has default value these methods raise "win_error" exception. Such design allows to write straightforward code for deleting series of keys without care whether they exist or not. Like this: basic_reg_key<char>(HKEY_CLASSES_ROOT, "").delete_subkey(".mp3", true).delete_subkey(".avi", true);.

template <class Str> basic_reg_key create_subkey(Str name,
    bool is_volatile=false, REGSAM access=KEY_ALL_ACCESS) const;

The method creates the subkey under current key and returns new "basic_reg_key" object which represents newly created subkey.

basic_reg_key& attach(HKEY key);
const HKEY detach();

The "attach" method closes any previous handle if it was opened, and associates specified key with the object. The "detach" method just breaks a linkage between internal raw handle and the object and returns the raw handle.

val_iterator begin_vals() const;
val_iterator end_vals() const;

range_vals vals() const;

First two methods return a value’s forward-type iterator that can be used for iteration through the current key’s values. Third method returns a proxy object having the "begin" and "end" methods returning the same iterators. Dereferenced iterator returns a pair whose first value is the registry value’s name stored in the std::basic_string, and the second one is DWORD type, representing the value’s type. The pair has a conversion operator to std::basic_string, which returns the value’s name. So, somebody can write something like this: "std::string s = *it;"

key_iterator begin_keys() const;
key_iterator end_keys() const;

range_keys keys() const;

The same as previous methods but return subkeys’ names’ iterators. Using them user can iterate through the names of subkeys. Dereferenced iterator returns std::basic_string object.

Additionally the class prohibits any comparisons between it and any other type because it is meaningless. Class can be tested whether it is represents actual key or not, using "operator unspecified_bool() const" implemented with "safe-bool" idiom (http://www.artima.com/cppsource/safebool.html).

Simple usage example

Assume we need to create some subkey and set its particular value. It can be done with next code:

reg_key(HKEY_CURRENT_USER, "Software")
    .create_subkey("my-subkey")
    .set_REGSZ_or_EXPAND("my-value", "this is the string");

The temporary object is created on the first line. It is used on the second line to create and return another temporary object representing subkey. And the last one is used on the third line to setup the value.

Another task may be to print subkeys’ names from HKEY_CLASSES_ROOT, which begin with ".c". This task can be done such way (additionally to STL, using boost’s "filter_iterator" adapter):

using namespace boost;
using namespace std;

bool start_with_c(const string &s)
{
    return s.find(".c") == 0;
}

// ...

cout << "'.c*' classes root subkeys:" << endl;
reg_key<char> k(HKEY_CLASSES_ROOT, "", KEY_READ);
copy(
    make_filter_iterator(&start_with_c, k.keys().begin(), k.keys().end()),
    make_filter_iterator(&start_with_c, k.keys().end(), k.keys().end()),
    ostream_iterator<string>(cout, "\n"));
cout << endl;

More sensible sample application can be found in the attached archive. It has been checked to be successfully built using "mingw" compiler (gcc version 4.4) and Microsoft compiler from Visual Studio 2008.

Source code

The "basic_reg_key" class implementation can be found in the attached archive inside the source code of the sample application. The class itself is implemented in the files "win-registry.cpp" and "win-registry.h". The application is delivered as the Eclipse project. It can be compiled either in the Eclipse IDE, or at a command line, using "cmake" build system (http://www.cmake.org).

Questions, comments, fill free to send me at the e-mail, which you can find in the source code.

Download source code

 
© 2011 Ãðèãîðüåâ Â.Â.