This is my first posting on CodeProject, so take it easy on me ;-). This is a set of macros and classes that I designed that make it much easier to deal with the registry. I've always liked the way that ATL makes a lot of tedious coding tasks appear to take on a declarative form and that is the spirit in which these classes were designed.
Where's the beef?
So, what problem am I trying to solve? Well, consider the following code that tries to access the registry to get a simple string value:
HKEY hSetting = NULL;
&hSetting) == ERROR_SUCCESS)
DWORD cbData = 0;
if (::RegQueryValueEx(hSetting, _T("Message"), NULL,
NULL, NULL, &cbData) == ERROR_SUCCESS)
LPTSTR szValue = _alloca(cbData);
NULL, NULL, (LPBYTE) szValue, &cbData);
Painful to say the least, and this doesn't even contain full error checking, and if the value never existed, you have to write all kinds of code to provide defaults. Well, that is what my code provides. It doesn't have a spiffy name, so I have to keep referring to it as my code or my classes / macros, so let's just call it CoolReg from here on out. CoolReg provides a way to declaratively access the registry so that the code above becomes:
CString strMessage = UserSettings.Message;
How can I accomplish this amazing feat? With some macros and a little
__declspec(property) kung-fu action! Let's take a look at how to use CoolReg from within an application.
_T("This application has never been run"))
DECLARE_SETTING(int, RunCount, 0)
DECLARE_SETTING(int, AnotherSetting, 80)
All that kung-fu is wrapped up in the macros used above (which is all given in the Settings.h file included in the demo project). The
BEGIN_SETTINGS_ROOT macro defines the base of a registry tree. The first argument is the name of the settings tree as seen in our source code (but in the actual source, this name will be appended with Settings, so that to access this tree, we must type UserSettings). The second argument is the registry key that the one we are defining will live under, and the third argument is the actual path to the registry key we are defining.
After that, we have several
DECLARE_SETTING macros being used all of which define some settings. A setting is defined by its type, name and default. In the first
DECLARE_SETTING, you can see that we are using
CString as the type, Message as the name and a string literal as the default. The name that you specify becomes the name of both the C++ property and the registry value.
After a couple of settings are defined, we use the
BEGIN_SETTINGS_GROUP macro to define a new sub key under the current key. The new sub key will be called, quite logically, MoreSettings and its parent must be given as the second argument of the macro which is of course User. After we are done defining the settings that make up this group, we must use the
END_SETTINGS_GROUP macro which will finish off the class we defined (which is really what all these macros are doing internally). Lastly, we must use the
DEFINE_SETTINGS_ROOT to give some storage to the roots that we defined above (though in actuality, these classes use very little storage space).
Enough, I Wanna Use It! How?
Well, it's actually really easy. To access the Message setting, simply type:
And to access AnotherSetting, simply type:
That's about all there is to it really.
Other cool stuff...
DECLARE_SETTING_EX macro provides some extra functionality that most people will find useful. Using this macro allows you to define your settings intended type in the registry. This allows you to declare a
CString setting but set its registry type to
REG_EXPAND_SZ. When you then try to access this property from your code, CoolReg will automagically expand the environment variables without you ever having to call
ExpandEnvironmentStrings (which many people forget to do).
DECLARE_SETTING_EX and its companion
DECLARE_SETTINGS_GROUP_EX also allow you the flexibility of specifying what name the setting / group should be mapped to in the registry.
Well, as with anything free, there are certain limitations (though not in your usage, feel free to use this wherever you like and modify to your heart's content). First, the names of your properties must be valid C++ identifiers, but registry property names have no such restriction, so if you are trying to graft this onto your existing applications, you may have to use the more verbose
_EX variants of these macros. I also had some code that would handle arbitrary data types through the use of templates, but when I wrote the sample app, I realized that this template code overrode my handling of
CString. What is really required is a template specialization method instead of the method I am currently using (which just relies on method overloading). This would be easy to provide, but I got a little bit lazy and decided to just comment out the template code that I had in place, but the slightly brave among you should find it easy to change this.
Update: doh!!! No one asked for it, but I decided to not be lazy and rewrote some of the internal classes and macros to use the template specialization method I mentioned above to achieve the benefits of arbitrary (well, not quite arbitrary) object storage. This is no longer a limitation, but you should be aware that if you try to use any types that contain pointers (
CComString, etc...), they will not work. This support is strictly for storing objects containing only simple types (such as
RECTs, etc...). The demo contains a sample of how to do this.
Lastly, if you try to do something like the following:
it will simply not work. This is a limitation of
__declspec(property), but the slightly less friendly:
UserSettings.RunCount += 1;
will work just fine. This may only be an issue with VC6 as I didn't test this under VC7, but the compiler will die with an internal compiler error (C1001) if you attempt to use the first code fragment above. This error basically means that the compiler cannot generate correct code for the first construct (which is understandable I guess).
One thing to realize is that every time you access one of these properties, an access is made to the registry. There is no caching of values whatsoever, so invoke these properties sparingly (in fact, in the above code, the
RunCount property is accessed twice, once to read the initial value and another to store the incremented value).
That's about it. Enjoy!!!