65.9K
CodeProject 正在变化。 阅读更多。
Home

利用 ATL CRegKey 类的头文件模板库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (15投票s)

2010年12月16日

CPOL

7分钟阅读

viewsIcon

47243

downloadIcon

612

在注册表中读写序列化/反序列化任何类型的任意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::stringstd::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 );
}

RegKeyMFCArchiveWriteRegKeyMFCArchiveRead 使用MFC序列化。

RegKeyArchiveWriteRegKeyArchiveRead 使用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.hppregistry_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 ) 时,与 SetBinaryValueQueryBinaryValue 相同的部分特化被执行。

字符串的内部机制

字符串(又名字符数组、std::stringCString)不能通过这个模板类“注册”,因为它们需要内存分配;字符串需要专门的函数。

这是为 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
}

CA2WCW2A 是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.hwin32_exception.h 文件,以便在 registry_key 库外部使用。
  • 2010年12月15日:首次上传。
© . All rights reserved.