声明式访问注册表设置






4.59/5 (24投票s)
一套宏和类,允许以声明方式访问注册表设置。
引言
这是我在 CodeProject 上的第一个帖子,所以请对我宽容 ;-)。这是一套我设计的宏和类,它们使得处理注册表变得更加容易。我一直很喜欢 ATL 让许多繁琐的编码任务看起来具有声明式风格的方式,而这些类正是本着这种精神设计的。
有什么用?
那么,我试图解决什么问题?好吧,考虑以下代码,它尝试访问注册表以获取一个简单的字符串值
HKEY hSetting = NULL; if (::RegCreateKey(HKEY_CURRENT_USER, _T("Software\\CodeProject\\RegistrySettings"), &hSetting) == ERROR_SUCCESS) { DWORD cbData = 0; if (::RegQueryValueEx(hSetting, _T("Message"), NULL, NULL, NULL, &cbData) == ERROR_SUCCESS) { LPTSTR szValue = _alloca(cbData); ::RegQueryValueEx(hSetting, _T("Message"), NULL, NULL, (LPBYTE) szValue, &cbData); } }
至少可以说很痛苦,而且这甚至不包含完整的错误检查,如果该值从未存在,您就必须编写各种代码来提供默认值。好吧,这就是我的代码所提供的。它没有一个花哨的名字,所以我不得不一直称它为我的代码或我的类/宏,所以我们从现在开始就称它为 CoolReg 吧。CoolReg 提供了一种声明式访问注册表的方式,以便上面的代码变成
CString strMessage = UserSettings.Message;
我如何完成这项惊人的壮举?通过一些宏和一点 __declspec(property)
的技巧!让我们来看看如何在应用程序中从内部使用 CoolReg。
BEGIN_SETTINGS_ROOT(User, HKEY_CURRENT_USER, _T("Software\\CodeProject\\RegistrySettings")) DECLARE_SETTING(CString, Message, _T("This application has never been run")) DECLARE_SETTING(int, RunCount, 0) BEGIN_SETTINGS_GROUP(MoreSettings, User) DECLARE_SETTING(int, AnotherSetting, 80) END_SETTINGS_GROUP(MoreSettings) END_SETTINGS_ROOT(User) BEGIN_SETTINGS_ROOT(Machine, HKEY_LOCAL_MACHINE, _T("SOFTWARE\\CodeProject\\RegistrySettings")) // no settings defined under HKEY_LOCAL_MACHINE yet END_SETTINGS_ROOT(Machine) DEFINE_SETTINGS_ROOT(User) DEFINE_SETTINGS_ROOT(Machine)
所有那些技巧都封装在上面使用的宏中(所有这些都在演示项目中包含的Settings.h 文件中)。BEGIN_SETTINGS_ROOT
宏定义了注册表树的根。第一个参数是我们源代码中看到的设置树的名称(但在实际源代码中,这个名称后面会附加 Settings,所以要访问这个树,我们必须输入 UserSettings)。第二个参数是我们正在定义的设置所属的注册表键,第三个参数是我们正在定义的注册表键的实际路径。
在此之后,我们有几个 DECLARE_SETTING
宏被使用,它们都定义了一些设置。一个设置由其类型、名称和默认值定义。在第一个 DECLARE_SETTING
中,您可以看到我们使用 CString
作为类型,Message 作为名称,以及一个字符串字面量作为默认值。您指定的名称将成为 C++ 属性和注册表值的名称。
在定义了几个设置之后,我们使用 BEGIN_SETTINGS_GROUP
宏来定义当前键下的一个新子键。新子键将被逻辑地命名为 MoreSettings,而它的父级必须作为宏的第二个参数给出,当然就是 User。在我们完成定义构成此组的设置后,我们必须使用 END_SETTINGS_GROUP
宏,它将完成我们定义的类(这实际上是所有这些宏在内部所做的事情)。最后,我们必须使用 DEFINE_SETTINGS_ROOT
来为上面定义的根提供一些存储(尽管实际上,这些类占用的存储空间非常少)。
够了,我想用它!怎么用?
好吧,实际上它非常简单。要访问 Message 设置,只需输入
UserSettings.Message
要访问 AnotherSetting,只需输入
UserSettings.MoreSettings.AnotherSetting
基本上就是这样了。
其他很酷的东西...
DECLARE_SETTING_EX
宏提供了一些大多数人会发现有用的额外功能。使用此宏允许您定义注册表中设置的预期类型。这允许您声明一个 CString
设置,但将其注册表类型设置为 REG_EXPAND_SZ
。当您然后尝试从代码中访问此属性时,CoolReg 将自动展开环境变量,而您无需调用 ExpandEnvironmentStrings
(许多人会忘记这样做)。DECLARE_SETTING_EX
及其伴侣 DECLARE_SETTINGS_GROUP_EX
还允许您灵活地指定设置/组应映射到注册表中的哪个名称。
限制
好吧,就像任何免费的东西一样,存在一些限制(尽管在您的使用方面没有,您可以随意在任何地方使用它并随意修改它)。首先,您的属性名称必须是有效的 C++ 标识符,但注册表属性名称没有这样的限制,因此如果您试图将其集成到您现有的应用程序中,您可能需要使用更冗长的 _EX
变体宏。我还编写了一些代码,可以通过模板处理任意数据类型,但在编写示例应用程序时,我意识到这个模板代码覆盖了我对 CString
的处理。真正需要的是模板特化方法,而不是我目前使用的方法(它只是依赖于方法重载)。提供这个很容易,但我有点懒,所以我决定注释掉我已有的模板代码,但稍微勇敢一点的人应该很容易对此进行更改。
更新: 哎呀!!!没有人要求,但我决定不再懒惰,并重写了一些内部类和宏,以使用我上面提到的模板特化方法来实现任意(好吧,不完全是任意)对象存储的好处。这不再是限制,但您应该意识到,如果您尝试使用包含指针的任何类型(_bstr_t
、CComString
等),它们将不起作用。此支持严格用于存储仅包含简单类型的对象(例如 WINDOWPLACEMENT
或 RECT
等)。演示中包含了一个如何做到这一点的示例。
最后,如果您尝试做以下事情
UserSettings.RunCount++;
它根本不会起作用。这是 __declspec(property)
的一个限制,但稍微不太友好的
UserSettings.RunCount += 1;
将可以正常工作。这可能只在 VC6 中是问题,因为我没有在 VC7 下测试过,但如果您尝试使用上面的第一个代码片段,编译器将因内部编译器错误 (C1001) 而崩溃。这个错误基本上意味着编译器无法为第一个构造生成正确的代码(我想这是可以理解的)。
需要认识到的一点是,每次访问这些属性之一时,都会访问注册表。根本没有值的缓存,所以请谨慎使用这些属性(事实上,在上面的代码中,RunCount
属性被访问了两次,一次读取初始值,一次存储增量后的值)。
就这样。享受吧!!!