Yet another registry class!? But why?
I recently read an article here on CodeProject that described a set of classes to work with the registry.
A request for a template version of the classes inevitably popped up. The author of that article had a
couple of reasons for not writing a template version. After thinking about it for a while I came to the
conclusion that a template could indeed be made. So here it is.
Usage
The class is really three classes. However, instead of making one class for each type to store I made
one template class for each type of data type that the registry supports. Well not really. Classes
for string, dword and binary storage are provided. I feel that these three cover most things one would
want to store in the registry.
So, say we want to store a string:
typedef registry_string<std::string> regstring;
regstring str("Software\\RegValueTest\\blah", HKEY_CURRENT_USER);
str = "Hello world!";
std::string stored = str;
std::cout << stored << std::endl;
First a convenience typedef. Our template type is
std::string
, that's the type we wish to load or store.
Because we use the
registry_string
template the string will be stored with type
REG_SZ
in the registry.
Storing is as easy as assigning to the
registring
object. Reading from the registry is also performed
this way, simply create an object of the right type and assign from the
regstring
.
All this is courtesy of operator= and operator T (conversion operator). If one doesn't like the use of
a conversion operator (they're not exactly the best thing in the world) it's enough just to change the
name of the functions.
To store a POINT:
typedef registry_binary<POINT> regpoint;
regpoint pnt("Software\\RegValueTest\\blah", HKEY_CURRENT_USER);
POINT p = {99,88};
pnt = p;
POINT stored = p;
std::cout << stored.x << "," << stored.y << std::endl;
The last class, registry_int, works the same way as the other two so an example should not be necessary.
These three classes can be used to store a large number of classes. A few examples:
typedef registry_string<std::string> regstring;
typedef registry_int<bool> regbool;
typedef registry_binary<RECT> regRECT;
typedef registry_string<double> regdouble;
A double in a registry_string?
The
registry_string
template class is quite handy. To be able to store an arbitrary type in a string a
std::stringstream
is used. This enables the use of this template class with any type that can serialize
itself to a stream. This includes the built in types;
bool
,
double
,
char
etc. The
registry_string<double>
will convert the double the user wishes to store into a textual representation of the value. This might
be better than storing a binary version of the number (but if you want to store it in binary then it's as simple as making a
registry_binary<double>
).
The classes are also capable of some other nice things:
registry_value val("Software\\RegValueTest\\blah", HKEY_CURRENT_USER);
if(val.exists())
{
val.remove_value();
val.remove_key();
}
This would check if the value existed and if that was the case remove both the value and the key. The
registry_value class is a base class for the other three. More on that in the next section.
Implementation
I put the functions that all three classes used in a common base class, registry_value. Here's a simplified
overview of that class:
class registry_value
{
public:
registry_value(const std::string & name, HKEY base_);
bool exists();
void remove_value(void);
void remove_key(void);
protected:
bool open(bool write_access);
void close(void);
bool query_value(DWORD * type, DWORD * size, void * buffer);
bool set_value(DWORD type, DWORD size, const void * buffer);
private:
void make_path(const std::string & name);
HKEY key;
HKEY base;
std::string valuename;
std::string keyname;
};
Nothing spectacular here. The public interface consists of the three functions described above. The protected
interface has functions for gaining access to a key and reading/writing. The private section includes a utility function and data that the other functions use. As described above, the class can be used on it's own to check if a value exists etc.
As clearly visible, registry_value
is not a template class. Let's take a closer look at
registry_binary
.
template<class T>
class registry_binary : public registry_value
{
public:
registry_binary(const std::string & name, HKEY base) :
registry_value(name, base)
{
}
The constructor simply forwards the parameters to the base class.
operator T()
{
T returnval = T();
if(open(false) == true)
{
DWORD type, size = sizeof(T);
if(query_value(&type, &size, &returnval) == true)
{
assert(type == REG_BINARY);
}
close();
}
return returnval;
}
The conversion operator handles reading from the registry. Nothing particularly noteworthy here
except perhaps the initialization of the value we store the data in. The reason for
'
= T();
'
is that it ensures that returnval doesn't contain random crap data since this value is returned
from the function unchanged if something goes wrong with the queries.
const registry_binary & operator=(const T & value)
{
if(open(true) == true)
{
set_value(REG_BINARY, sizeof(T), &value);
close();
}
return *this;
}
The
operator=
handles writing to the registry. The
set_value
function defined in
registry_value
and it wants a type, a size and a pointer to the data. That's all there is to
registry_binary
.
Closing words
This article is not so much about registry access itself but more about showing how templates can be used to get more functionality with less code.
As a side note: there is also a specialized version for registry_string<std::string>
that omits the
stringstream
code that registry_string
normally uses.