利用 ATL CRegKey 类的头文件模板库
在注册表中读写序列化/反序列化任何类型的任意C++数据结构。
目录
引言
这个ATL CRegKey
类的辅助工具允许您读取/写入任何类型的数据结构,而无需进行类型转换或特定于类型的操作。
#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 );
}
第一个 RegKeyWrite
以二进制数据(Winnt.h中定义的REG_BINARY
关键字)的形式存储,第二个以空终止字符串(REG_SZ
)的形式存储,最后一个以32位数字(REG_DWORD
)的形式存储。这些都可以使用RegEdt32.exe Microsoft实用程序或任何其他注册表编辑器进行编辑。
写入注册表后,您需要读回数据结构。
void RegLoad( CRegKey& RegKey )
{
myStruct ms = RegKeyRead( RegKey, "myStruct", ms );
CString LastSaved = RegKeyRead( RegKey, "LastSaved", LastSaved );
bool Flag = RegKeyRead( RegKey, "Flag", Flag );
... // some processing
}
背景
这个库是一个仅头文件库;您只需要 #include <registry_key.h>
。
该文件包含一组内联函数;它利用了MFC/ATL CRegKey
类。您必须使用的唯一 CRegKey
成员函数是 CRegKey::Open
,不需要其他背景知识。它是使用Microsoft Visual C++ 2010开发的,并且仅通过它进行了测试。这些函数主要是模板函数,但使用它不需要模板知识。
使用代码
如上两个小示例所示,如果要将字符串或基本结构读/写到注册表,则需要 #include <registry_key.h>
并 using namespace registry_key;
,仅此而已。任何一种常见的字符串类型都可用,“C”风格,std::string
,std::wstring
,以及出色的MFC/ATL CString
。
“基本结构”应理解为可以按字节复制的数据结构,因此它不能包含指向内存地址的指针或虚函数。例如,MFC/ATL CRect
类是基本结构,但MFC CObject
本身或从 CObject
派生的类则不是。
由于C++数据结构并非总是“基本”的,因此本文的下一步将向您展示如何从/向派生自MFC CObject
的注册表对象,或者STL容器,或者更通用地,需要动态内存分配和/或存在指向共享数据的指针的任何类型的数据结构进行读/写。
不要重新发明轮子,这众所周知为“序列化”,有时称为“归档”或“持久化”。我利用了两种实现:MFC CArchive
和“boost serialize”。因此,如果您想将任意C++数据结构集序列化到注册表键,以下是MFC开发者的基本示例。
#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 );
}
以及标准模板库(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
和 RegKeyMFCArchiveRead
使用MFC序列化。
RegKeyArchiveWrite
和 RegKeyArchiveRead
使用boost序列化;STL容器的序列化得到本地支持。
只有当您在 #include <registry_key.h>
之前 #include <afx.h>
和/或 #include <boost/archive/basic_archive.hpp>
时,这些函数才可用。如果您不包含这些,则注册表键库仍然可用(除了存档函数)。
存档产生的字节序列存储在 REG_BINARY
键中。
boost序列化相关
boost序列化比MFC拥有更多功能,但学习曲线更长。也许需要一个“boost序列化生存工具箱”的提示/技巧?无论如何,为了启用STL序列化,它需要特定的包含文件;在此示例中,您会注意到两个新的包含文件:boost/archive/basic_archive.hpp 在 registry_key.h 之前,以及 boost/serialization/vector.hpp 在任何其他地方(在此情况下是在之后)。basic_archive.hpp 文件是 boost::serialize 库文件,并且必须在 registry_key.h 之前包含,才能启用注册表键库中使用存档。boost/serialization/vector.hpp 文件启用了 std::vector
容器的存档,与 boost/serialization/list.hpp 启用 std::map
容器的存档方式相同,依此类推,适用于每个标准的C++容器(set、map、list、...)。这是 boost::serialize 的功能,与 registry_key 无关。更多功能在 console_sample 项目中显示;也可以查看 registry_key.h 文件中的 boost 序列化部分。
如果读/写失败怎么办?
您会注意到,没有错误代码;这是一个设计决定,因此当出现问题时,会抛出 regkey_exception
。开发者有责任捕获它(或不捕获)。请注意,您也可以遇到特定于存档的异常。
作为这个类的第一个用户,我开发了一个小类来正确捕获异常并返回一个“false”值。我使用 boost optional 库来实现这一点;WTLsample 展示了一种处理异常的方法。
内部机制
该库以非常经典的形式使用模板函数。
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 );
和
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
是一个类模板,稍后会解释。
您可能会注意到 RegKeyRead< int >
使用显式模板特化(< int >
)。这是因为模板参数仅用于返回值,而C++不允许基于返回值的函数重载。为了简化此库的使用,并使其对不了解模板语法的开发者可用,我创建了这个函数。
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 );
RegKeyRead
的第三个参数仅在编译时使用,用于函数特化。
模板< class _T, rgType _H = blah blah bla >_TRegKey 的内部机制
_Type
参数可以是任何类型的结构,基于字节大小计数;它将被视为 DWORD
或通过 _TRegKey
的第二个参数(blah blah blah
关键字可能在未来的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& );
};
因此,我用第二个参数的两个可能值对这个模板进行了部分特化。
// 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 );
}
};
当 sizeof( _Type ) > sizeof( DWORD )
时,与 SetBinaryValue
和 QueryBinaryValue
相同的部分特化被执行。
字符串的内部机制
字符串(又名字符数组、std::string
和 CString
)不能通过这个模板类“注册”,因为它们需要内存分配;字符串需要专门的函数。
这是为 CString
特化的 RegKeyRead
模板函数。
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 ;
}
CString
会隐藏 Unicode 或 ANSI 字符集的用法,但如果您需要显式使用,可以使用 std::string
表示 ANSI,使用 std::wstring
表示 Unicode。注册表将字符串存储为 Unicode,但会根据您的设置进行自动转换。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
和 CW2A
是ATL定义的宏,请参阅 此处 的文档。
序列化的内部机制
要使用序列化,您必须使用一个名为 RegKeyMFCArchiveWrite RegKeyMFCArchiveRead
(以及 RegKeyArchiveWrite RegKeyArchiveRead
用于 boost 对等版本)的函数。
原理是利用 CMemFile
类将数据存储到内存缓冲区,然后使用 SetBinaryValue
写入该缓冲区,反之亦然,通过从 QueryBinaryValue
获取的内存缓冲区加载;这对MFC来说非常容易。
这是MFC存档的代码;boost序列化的代码有点棘手,但非常相似。
这是 RegKeyMFCArchiveWrite
函数。
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 );
}
这是 RegKeyMFCArchiveRead
函数。
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 ;
}
请注意,会返回 _Type Value
;如果不是指针,它应该有一个移动构造函数(也称为右值引用);请参阅 MSDN 文章 或 CodeProject 文章。
里面有什么?
解压缩源代码将创建一个根目录 registry_key,其中包含 registry_key.h 文件。在同一目录中是 registry_key.sln - Visual Studio 解决方案文件,其中包含三个子目录中的三个示例。与 registry_key.h 一样,这些示例都经过了充分的文档记录,我建议您试用它们。console_sample 没有 GUI,但内容丰富,并明确展示了所有可能性 - 如果您只想看一个示例,就是它。MFCsample 展示了如何保存主窗口的位置和编辑控件的内容。WTLsample 更复杂,因为它通过特定于应用程序的类实现了 boost optional。如果您不喜欢 registry_key 发送异常的方式,请查看它。
兴趣点
由于 registry_key 利用了 CRegKey
MFC/ATL 类,您可以访问其文档 此处。如果您想了解更多关于序列化的信息,请参考 boost 序列化文档的概述章节 此处。MFC 中的序列化描述 此处。boost optional 库的描述 此处。boost 库可以从 www.boost.org 以源代码形式下载,或者从 www.boostpro.com 以二进制安装程序形式下载。使用后者时,您不必构建库。WTL 可以从 sourceforge.net 下载,它是一个仅头文件的库。
历史
- 2010年1月11日
- 添加了目录。
- 添加了“内部机制”章节。
- 在 registry_key.h 中移除了部分不当用语。
- 创建了 mem_stream.h 和 win32_exception.h 文件,以便在 registry_key 库外部使用。
- 2010年12月15日:首次上传。