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

声明式访问注册表设置

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (24投票s)

2004年4月13日

CPOL

5分钟阅读

viewsIcon

94233

downloadIcon

525

一套宏和类,允许以声明方式访问注册表设置。

引言

这是我在 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_tCComString 等),它们将不起作用。此支持严格用于存储仅包含简单类型的对象(例如 WINDOWPLACEMENTRECT 等)。演示中包含了一个如何做到这一点的示例。

最后,如果您尝试做以下事情

UserSettings.RunCount++;

它根本不会起作用。这是 __declspec(property) 的一个限制,但稍微不太友好的

UserSettings.RunCount += 1;

将可以正常工作。这可能只在 VC6 中是问题,因为我没有在 VC7 下测试过,但如果您尝试使用上面的第一个代码片段,编译器将因内部编译器错误 (C1001) 而崩溃。这个错误基本上意味着编译器无法为第一个构造生成正确的代码(我想这是可以理解的)。

需要认识到的一点是,每次访问这些属性之一时,都会访问注册表。根本没有值的缓存,所以请谨慎使用这些属性(事实上,在上面的代码中,RunCount 属性被访问了两次,一次读取初始值,一次存储增量后的值)。

就这样。享受吧!!!

© . All rights reserved.