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 );
... }
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 );
}
RegKeyWrite( RegKey, "iFlag", 32 );
and:
template< class _Type >
_Type RegKeyRead( CRegKey& RegKey, const TCHAR* Key )
{
return _TRegKey< _Type >( RegKey ).Read( Key );
}
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 );
}
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.
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 CString
s 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( ... ){
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( ... ) {
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.