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

Header-only template library leveraging the ATL CRegKey class

0.00/5 (No votes)
14 Jan 2011 1  
Read/write serialize/unserialize any kind of arbitrary set of C++ data structures in the Registry.

Table of Contents

Introduction

This helper for the ATL CRegKey class allows you to read/write any kind of structure/class without cast or a type specific operation:

#include <registry_key.h>
using namespace registry_key;
 
struct myStruct
{
    POINT Point;
    RECT Rect;
}; 
 
void RegSave( CRegKey& RegKey, const myStruct& ms, bool Flag )
{ 
    RegKeyWrite( RegKey, "myStruct", ms );
    RegKeyWrite( RegKey, "LastSaved",
        CTime::GetCurrentTime().Format( "%A, %B %d, %Y" );
    RegKeyWrite( RegKey, "Flag", Flag ); 
}

First RegKeyWrite stores as binary data (REG_BINARY keyword as defined in Winnt.h), second as null-terminated string (REG_SZ), and last one as a 32 bit number (REG_DWORD). These are editable with the RegEdt32.exe Microsoft utility or with any other Registry editor.

After writing to the Registry, you want to read back the data structures:

void RegLoad( CRegKey& RegKey )
{ 
    myStruct ms = RegKeyRead( RegKey, "myStruct", ms ); 
    CString LastSaved = RegKeyRead( RegKey, "LastSaved", LastSaved );
    bool Flag = RegKeyRead( RegKey, "Flag", Flag );
   ... // some processing
}

Background

This library is a header-only library; you only need to #include <registry_key.h>.

This file contains a set of inline functions; it leverages the MFC/ATL CRegKey class. The only CRegKey member function you have to use is CRegKey::Open, no more specific background is needed. It has been developed with Microsoft Visual C++ 2010, and tested only with it. The functions are mainly templated functions, but no template knowledge is needed in order to use it.

Using the code

As shown by the two small samples above, if you want to read/write to the Registry only strings or basic structures, you need to #include <registry_key.h> and using namespace registry_key;, that's all. Any kind of well known string is available, "C" style, std::string, std::wstring, and the wonderful MFC/ATL CString.

The "basic structure" has to be understood as a data structure that you can copy byte to byte, so it must not contain a pointer to memory address or a virtual function. For example, the MFC/ATL CRect class is a basic structure, but the MFC CObject itself or a derived class from CObject is not.

As C++ data structures are not always "basic", the following step of this article shows you how to read/write from/to a Registry object derived from the MFC CObject, or an STL container, or less specific, any kind of data structure where dynamic memory allocation is required and/or there is a pointer to shared data.

Don't reinvent the wheel, this is well known as "serialization", some times named "archiving" or "persistence". I leverage on two implementations: the MFC CArchive and the "boost serialize". So if you want to serialize your arbitrary set of C++ data structures to a Registry key, here is a basic sample for MFC developers:

#include <afx.h> // in order to access MFC CArchive
#include <registry_key.h>

class CMyClass : public CObject
{
    CRect m_Rect;
public:
    CRect Rect;
    CMyClass()
    {}
    void Serialize(CArchive& ar)
    {
        CObject::Serialize( ar );
        if( ar.IsLoading() )
            ar >> m_Rect;
        else
            ar << m_Rect ;
    }
    DECLARE_SERIAL( CMyClass );
};
IMPLEMENT_SERIAL( CMyClass, CObject, VERSIONABLE_SCHEMA | 2)

using namespace registry_key;
 
void RegSave( CRegKey& RegKey, CMyClass* Ptr )
{
    RegKeyMFCArchiveWrite( RegKey, L"MFC Object", Ptr );
}
 
CMyClass* RegLoad( CRegKey& RegKey )
{
   return RegKeyMFCArchiveRead( RegKey, L"MFC Object", Ptr );
}

And for the standard template library (STL):

#include <boost/archive/basic_archive.hpp> // in order to have boost::serialize available
#include <registry_key.h>

#include <boost/serialization/vector.hpp> // std::vector container archivable
 
using namespace registry_key;
 
void RegSave( CRegKey& RegKey, const std::vector< int >& Vector )
{
   RegKeyArchiveWrite( RegKey, "std::vector< int > Value"), Vector );
}
 
void RegLoad( CRegKey& RegKey, std::vector< int >& Vector )
{
   Vector = RegKeyArchiveRead( RegKey, "std::vector< int > Value"), Vector );
}

RegKeyMFCArchiveWrite and RegKeyMFCArchiveRead use MFC serialization.

RegKeyArchiveWrite and RegKeyArchiveRead use boost serialization; serialization of STL containers are natively supported.

These functions are available only if you #include <afx.h> and/or #include <boost/archive/basic_archive.hpp> before #include <registry_key.h>. If you don't include these, the registry_key library is still usable (except for the archive functions).

The sequence of bytes resulting from an archive is stored in a REG_BINARY key.

boost serialization specific

boost serialization has a lot more features than MFC, but the learning curve is longer. Maybe a "boost serialization survival kit" tip/tricks should be needed? Anyway, in order to enable STL serialization, it requires specific included files; in this sample, you will notice two new included files: boost/archive/basic_archive.hpp before registry_key.h and boost/serialization/vector.hpp anywhere else (after, in this case). The file basic_archive.hpp is a boost::serialize lib file, and must be included before registry_key.h in order to enable the use of archives in the registry_key library. The file boost/serialization/vector.hpp enables archiving of the std::vector container, in the same way boost/serialization/list.hpp enables archiving of the std::map container, and so on for every std container (set, map, list, ...). This is a boost::serialize functionality, nothing is specific to registry_key. More features are shown in the console_sample project; also have a look at the boost serialization section in the registry_key.h file.

What if Read/Write fails?

As you will notice, there is no error code; this is a design decision, so when something goes wrong, a regkey_exception is thrown. It is the responsibility of the developer to catch it (or not). Be aware that you can also have an archive specific exception.

As the first user of this class, I developed a small class in order to properly catch exceptions and return a "false" value. I use the boost optional library in order to do that; the WTLsample shows a way to handle exceptions.

Under the hood

This library uses templated functions in some very classic form:

template< class _Type >
void RegKeyWrite( CRegKey& RegKey, const TCHAR* Key, const _Type& Value )
{
    _TRegKey< _Type >( RegKey ).Write( Key, Value ); 
}

// instantiation example
RegKeyWrite( RegKey, "iFlag", 32 );

and:

template< class _Type >
_Type RegKeyRead( CRegKey& RegKey, const TCHAR* Key )
{
    return _TRegKey< _Type >( RegKey ).Read( Key ); 
}

// explicit instantiation example
int iFlag = RegKeyRead< int >( RegKey, "iFlag" );

_TRegKey is a class template explained later.

You may notice that RegKeyRead< int > uses an explicit template specialization (the < int >). This is because the template parameter is only used for the returned value, and C++ doesn't allow to overload a function based on its return value. In order to simplify usage of this library and make it available to developers unaware of template syntax, I made this function:

template< class _Type >
_Type RegKeyRead( CRegKey& RegKey, const TCHAR* Key, const _Type& )
{
    return RegKeyRead< _Type >( RegKey, Key );
}

// automatic instantiation example
int iFlag = RegKeyRead( RegKey, "iFlag", iFlag );

The third parameter of RegKeyRead is only used at compile time, in order to make a specialization of this function.

Under the hood of template< class _T, rgType _H = blah blah bla >_TRegKey

The _Type parameter can be any kind of structure, based on the count of byte size; it will be seen as a DWORD or as a byte buffer through the second parameter of _TRegKey (blah blah blah keyword may be in future TR2).

template< class _Type, 
    RegKeyType _How = sizeof( _Type ) <= sizeof( DWORD ) ? 
                      _RegKeyDword : _RegKeyRawBinary >
class _TRegKey
{
public:
    _Type Read( const TCHAR* Key );
    void Write( const TCHAR* Key, const _Type& );
};

So I partially specialize this template with the two possible values of the second parameter.

// used when sizeof( _Type ) is less or equal to sizeof( DWORD )
template< class _Type > 
class _TRegKey< _Type, _RegKeyDword >
{
    public:
    CRegKey& m_RegKey;

    _TRegKey( CRegKey& RegKey )
        :m_RegKey( RegKey )
    {}

    _Type Read( const TCHAR* Key ) throw( regkey_exception )
    {
        static_assert( sizeof( _Type ) <= sizeof( DWORD ), 
            "sizeof( _Type ) must be less or equal to sizeof( DWORD )" );

        DWORD Value;
        LONG Err = m_RegKey.QueryDWORDValue( Key, Value );
        if( ERROR_SUCCESS != Err )
            throw regkey_exception( Err );
        _Type rValue = static_cast< _Type >( Value );
        return rValue ;
    }
    void Write( const TCHAR* Key, const _Type& Value )  throw( regkey_exception )
    {
        static_assert( sizeof( _Type ) <= sizeof( DWORD ), 
            "sizeof( _Type ) must be less or equal to sizeof( DWORD )" );

        DWORD dwValue = Value;
        LONG Err = m_RegKey.SetDWORDValue( Key, dwValue );
        if( ERROR_SUCCESS != Err )
            throw regkey_exception( Err );
    }
};

When sizeof( _Type ) > sizeof( DWORD ), the same kind of partial specialization is done with SetBinaryValue and QueryBinaryValue.

Under the hood of Strings

Strings (a.k.a. array of characters, std::string, and CString) can't be "registered" through this template class as they need memory allocation; strings need specialized functions.

Here is the RegKeyRead template function specialized for CString:

template<>
CString RegKeyRead< CString >( CRegKey& RegKey, 
                       const TCHAR* Key )  throw( regkey_exception )
{
    ULONG Size = 0;
    LONG Err = RegKey.QueryStringValue( Key, NULL, &Size );
    if( ERROR_SUCCESS != Err )
        throw regkey_exception( Err );
    CString Value;
    Err = RegKey.QueryStringValue( Key, Value.GetBufferSetLength( Size ), &Size );
    if( ERROR_SUCCESS != Err )
        throw regkey_exception( Err );
    Value.ReleaseBuffer();
    return Value ;
}
Working with CStrings hides the use of the Unicode or ANSI character set, but if you need it explicitly, you may use std::string for ANSI and std::wstring for Unicode. The Registry stores strings as Unicode, but automatic conversion is made depending on your settings.
template<>
std::wstring RegKeyRead< std::wstring >( CRegKey& RegKey, 
             const TCHAR* Key ) throw( regkey_exception )
{
    CString Value = RegKeyRead< CString >( RegKey, Key );
#ifdef _UNICODE
    return std::wstring( Value );
#else
    return std::wstring( CA2W( Value ) );
#endif
}

template<>
std::string RegKeyRead< std::string >( CRegKey& RegKey, 
            const TCHAR* Key ) throw( regkey_exception )
{
    CString Value = RegKeyRead< CString >( RegKey, Key );
#ifdef _UNICODE
    return std::string( CW2A( Value ) );
#else
    return std::string( Value );
#endif
}

CA2W and CW2A are macros defined by ATL, see the doc here.

Under the hood of Serialization

In order to use serialization, you have to use a function named RegKeyMFCArchiveWrite RegKeyMFCArchiveRead (and RegKeyArchiveWrite RegKeyArchiveRead for the boost counterpart).

The principle is to store to a memory buffer, thanks to the CMemFile class, and then write this buffer with SetBinaryValue, and back with loading from the memory buffer obtained with QueryBinaryValue; very easy with MFC.

Here is the code for the MFC archive; the one for boost serialize is a little bit tricky, but very similar.

Here is the RegKeyMFCArchiveWrite function:

template< class _Type >
void RegKeyMFCArchiveWrite( CRegKey& RegKey, const TCHAR* Key, const _Type& Value 
    ) throw( ...  )// regkey_exception and CArchiveException
{
    CMemFile File;
    {
        CArchive ar( &File, CArchive::store );
        ar << Value ;
    }
    ULONGLONG Size =  File.GetLength( );
    BYTE* Ptr = File.Detach();

    LONG Err = RegKey.SetBinaryValue( Key, Ptr, Size );
    free( Ptr );

    if( ERROR_SUCCESS != Err )
        throw regkey_exception( Err );
}

Here is the RegKeyMFCArchiveRead function:

template< class _Type >
_Type RegKeyMFCArchiveRead( CRegKey& RegKey, const TCHAR* Key
    )  throw( ... ) // regkey_exception and CArchiveException
{
    DWORD BufferSize = 0;
    LONG Err = RegKey.QueryBinaryValue( Key, NULL, &BufferSize );
    if( ERROR_SUCCESS != Err )
        throw regkey_exception( Err );

    std::vector< BYTE > Vec( BufferSize );
    Err = RegKey.QueryBinaryValue( Key, &Vec[0], &BufferSize );
    if( ERROR_SUCCESS != Err )
        throw regkey_exception( Err );
    CMemFile File( &Vec[0], BufferSize );

    _Type Value;
    {
        CArchive ar( &File, CArchive::load );
        ar >> Value ;
    }

    return Value ;
} 

Note that _Type Value is returned; if not a pointer, it should have a move constructor (a.k.a. R-value reference); see the MSDN article or the CodeProject one.

What's in the box?

Unzipping the source code will create a root directory registry_key where the registry_key.h file is included. In this same directory is registry_key.sln - the Visual Studio solution file with three samples in three subdirectories. Like registry_key.h, these samples are highly documented and I recommend you to play with them. console_sample does not have any GUI, but it is rich, and it shows explicitly every possibility - if you want to look at only one sample, this is the one. MFCsample shows how to save the main window position and the content of an edit control. WTLsample is more sophisticated as it implements boost optional through an application specific class. If you don't like the way registry_key sends exceptions, have a look at it.

Points of interest

As registry_key leverages on the CRegKey MFC/ATL class, you may access its documentation here. If you want to know a little more on what is serialization, refer to the Overview chapter of boost serialize documentation here. Serialization in MFC is described here. The boost optional library is described here. boost libraries can be downloaded in source code form from www.boost.org, or as a binary installer from www.boostpro.com. You will not have to build the libraries with the second one. WTL can be downloaded at sourceforge.net, it is a header only library.

History

  • January 11, 2010
    • Added Table of contents.
    • Added Under the hood chapter.
    • Some slags removed in registry_key.h.
    • mem_stream.h and win32_exception.h created in order to be used even outside of the registry_key lib.
  • December 15 2010: First upload.

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