注册表在系统配置和系统控制方面扮演着非常重要的角色,它是操作系统和用户应用程序的设置仓库。注册表可能是存储在磁盘上的静态数据,也可能是一系列由Windows内核负责维护的内存中的数据。
8.3.1 注册表的结构
注册表是一个数据库,它的结构同逻辑磁盘类似。注册表包含键(Key),它类似磁盘中的目录,注册表还包含键值(Value),它类似磁盘中的文件。一个键可以包含多个子键和键值,其中键值用于存储数据。顶层键称为根键。本节不对键和子键加以区分(仅根键不是子键)。
注册表共有6个根键,它们分别存储下列信息:
HKEY_CURRENT_USER 存储与当前登陆用户相关的信息
HKEY_CURRENT_USER 存储机器上所有帐户的信息
HKEY_CLASSES_ROOT 存储文件关联和COM(Component Object Model,组件对象模型) 对象注册信息
HKEY_LOCAL_MACHINE 存储系统相关信息
HKEY_PERFORMANCE_DATA 存储性能信息
HKEY_CURRENT_CONFIG 存储硬件配置信息
键值存储不同的数据类型,最常用的有以下3种:
REG_DWORD 双字型变量。可以存储数字或者布尔型变量
REG_BINARY 二进制数据。可以存储长度超过32位的数字和原始数据,比如加密密码
REG_SZ 字符串变量
为了更清楚地了解键和键值的关系及用途,可以运行Windows自带的注册表编辑器程序Regedit.exe,看看注册表的结构。按快捷键<Windows键>+R打开“运行”对话框,在打开窗口输入“regedit”,单击确定按钮,将打开注册表编辑器Regedit,如图8.2所示。
8.3.2 管理注册表
1.打开和关闭子键
读写注册表之前,必须先将目标子键打开,取得这个键对应的句柄。完成这项操作的函数是RegCreateKeyEx。除了打开已存在的子键外,这个函数也可以创建不存在的子键。如果被创建子键的上层键不存在,则同时创建上层子键。函数用法如下。
LONG RegCreateKeyEx(
HKEY hKey, // 指定父键的句柄
LPCTSTR lpSubKey, // 要打开的子键名称,这个子键必须是hKey参数指定键的子键
DWORD Reserved, // 保留,必须为0
LPTSTR lpClass, // 定义一个类名,一般为0
DWORD dwOptions, // 创建子键时的选项
REGSAM samDesired, // 指定子键的打开方式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指定键句柄的继承性
PHKEY phkResult, // 指向一个双字变量,用于返回创建或者打开的子键的句柄
LPDWORD lpdwDisposition // 作为输入时指定子键不存在时,是否要创建子键;作为输出时,
); // 返回值指定到底是创建了新键,还是打开了存在的键,可以设为NULL
(1)hKey参数是一个打开的键的句柄。此句柄必须是由RegCreateKeyEx或RegOpenKeyEx函数(用于打开子键的另一个函数)返回的,或者是上一小节预定义的几个根键句柄之一,如HKEY_LOCAL_MACHINE。
(2)dwOptions参数是创建子键时的选项,它可以是下列取值之一:
REG_OPTION_NON_VOLATILE 这是默认值。信息被保存到文件中,系统重启时被保留
REG_OPTION_VOLATILE 创建易失性子键,子键被保存在内存中,系统重启时子键消 失。Windows 9x不支持这个选项
(3)samDesired参数指定了子键的打开方式,根据需要可以使用下列取值的组合。只有指定了打开方式,才能对打开的子键进行相应的操作:
KEY_ALL_ACCESS 允许所有的存取
KEY_CREATE_LINK 允许建立符合列表
KEY_CREATE_SUB_KEY 允许建立下一层子键
KEY_ENUMERATE_SUB_KEYS 允许枚举下一层子键
KEY_EXECUTE 允许读操作
KEY_QUERY_VALUE 允许查询键值数据
KEY_SET_VALUE 允许修改或创建数据值
最后一个参数用于指定和返回部属信息,可能是下面两个值之一:
REG_CREATED_NEW_KEY 指定的键不存在,函数将它创建
REG_OPENED_EXISTING_KEY 指定的键存在,函数将它打开
如果函数执行成功,返回值是ERROR_SUCCESS,并且在phkResult参数指向的变量中返回打开的子键句柄。
当不再需要使用子键句柄时,要使用RegCloseKey函数将它关闭。
LONG RegCloseKey(HKEY hKey);
2.创建和删除子键
创建子键的函数是RegCreateKeyEx,删除子键的函数是RegDeleteKey。
LONG RegDeleteKey(
HKEY hKey, // 要删除的键的父键句柄
LPCTSTR lpSubKey // 要删除的子键的名称
);
只有所有到指定子键的句柄都关闭以后,这个子键才会被删除。
要删除的子键必须没有子键,也就是说它必须是最后一层子键。比如注册表中存在一个子键“HKEY_LOCAL_MACHINE\Key1\Key2
”,如果hKey指定为HKEY_LOCAL_MACHINE,lpSubKey指定为“Key1\Key2
”,这个函数仅删除Key2子键,不会连同Key1一同删除。
注意,在Windows 9x下,这个函数会删除所有下层子键和键值。
3.键值操作
在指定子键下,设置键值内容和创建键值的函数都是RegSetValueEx,用法如下。
LONG RegSetValueEx(
HKEY hKey, // 键值所在子键的子键句柄
LPCTSTR lpValueName, // 要设置的键值名称
DWORD Reserved, // 保留,必须为0
DWORD dwType, // 要设置的键值的数据类型
const BYTE* lpData, // 指向包含键值数据的缓冲区
DWORD cbData // lpData参数指向的缓冲区的长度
);
dwType参数指定了要设置的键值的数据类型,它可以是下面的取值之一:
REG_BINARY 任意结构的二进制数据
REG_DWORD 32位的双字
REG_EXPAND_SZ 扩展字符串。可以包含未展开的环境变量,例如“%PATH%”等
REG_MULTI_SZ 字符串数组,格式为“string1\0string2\0string3\0laststring\0\0”
REG_SZ 普通UNICODE字符串
读取键值数据类型和数据内容的函数是RegQueryValueEx,用法如下。
LONG RegQueryValueEx(
HKEY hKey, // 键值所在子键的子键句柄
LPCTSTR lpValueName, // 键值名称
LPDWORD lpReserved, // 保留,必须为0
LPDWORD lpType, // 返回键值的数据类型
LPBYTE lpData, // 返回键值的数据内容
LPDWORD lpcbData // 指定lpData参数指向的缓冲区的长度,
); // 函数也在这里返回复制到lpData中的数据长度
如果仅需要查询键值中数据的长度而不需要返回实际的数据,可以将lpData参数设置为NULL,但是lpcbData参数不能是NULL,这时函数会在lpcbData参数指向的双字变量中返回键值数据的长度。但如果想查询键值的类型,也可以同时将lpcbData和lpData参数设置为NULL。
读取键值数据时,可以两次调用RegQueryValueEx函数,第一次调用是为了查询键值数据类型,如果类型符合要求就再次调用这个函数读出实际数据。
删除键值的函数是RegDeleteValue,用法如下。
LONG RegDeleteValue(
HKEY hKey, // 键值所在子键的子键句柄
LPCTSTR lpValueName // 要删除的键值的名称
);
以上这3个函数调用成功后的返回值都是ERROR_SUCCESS。
8.3.3 注册表API应用举例(设置开机自动启动)
注册表键值定义的资料一般都要靠自己平时搜集整理,这里仅举一个简单的例子来演示注册表函数的使用方法。
Windows在启动并执行登陆操作后,会将HKEY_LOCAL_MACHINE\Software\Microsoft \Windows\CurrentVersion\Run子键下的所有键值项枚举一遍,并将所有REG_SZ类型的键值项中的数据当做一个文件名自动执行,所以在这个子键下设置一个键值项,让它的键值数据是某个文件名字符串,就可以让Windows启动后自动运行这个文件。
Windows只关心键值数据,并不关心键值名称,所以在设置的时候只要保证键值名称是惟一的就可以了。下面的代码可以用来将程序本身设置为自动运行。
08 SelfRun工程下
int main(int argc, char* argv[]) // 09 SelfRun工程下
{
// 根键、子键名称、和到子键的句柄
HKEY hRoot = HKEY_LOCAL_MACHINE;
char *szSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
HKEY hKey;
// 打开指定子键
DWORD dwDisposition = REG_OPENED_EXISTING_KEY; // 如果不存在不创建
LONG lRet = ::RegCreateKeyEx(hRoot, szSubKey, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition);
if(lRet != ERROR_SUCCESS)
return -1;
// 得到当前执行文件的文件名(包含路径)
char szModule[MAX_PATH] ;
::GetModuleFileName (NULL, szModule, MAX_PATH);
// 创建一个新的键值,设置键值数据为文件名
lRet = ::RegSetValueEx(hKey, "SelfRunDemo", 0, REG_SZ, (BYTE*)szModule, strlen(szModule));
if(lRet == ERROR_SUCCESS)
{
printf(" 自动启动设置成功!\n");
}
// 关闭子键句柄
::RegCloseKey(hKey);
getchar();
return 0;
}
GetModuleFileName函数可以取得指定模块的磁盘镜像文件名(包含路径),当传给它的句柄(第一个参数)为NULL时,该函数就取得主模块的文件名。
程序执行成功后,再重新启动电脑时,Windows将自动启动此程序。
8.3.4 ATL库的支持(CRegKey类)
每次都用API函数读写注册表比较麻烦,因此很多人都试图将操作注册表的API函数封装到一个类里面。其实在VC++环境中,Microsoft已经提供了一个操作注册表的类CRegKey。CRegKey不是MFC类,而是一个ATL类,它定义在atlbase.h文件中,使用时应该包含此头文件。CRegKey类中几个常用的成员函数描述如下:
(1)打开存在的注册表键值。
LONG Open(HKEY hKeyParent, LPCTSTR lpszKeyName, REGSAM samDesired = KEY_READ | KEY_WRITE);
(2)创建新的注册表键值,如果该键值已经存在,则打开这个键值。
LONG Create(HKEY hKeyParent, LPCTSTR lpszKeyName,
LPTSTR lpszClass = REG_NONE, DWORD dwOptions = REG_OPTION_NON_VOLATILE,
REGSAM samDesired = KEY_READ | KEY_WRITE,
LPSECURITY_ATTRIBUTES lpSecAttr = NULL,
LPDWORD lpdwDisposition = NULL);
(3)获取注册表中指定键值的数据。
LONG QueryValue(DWORD& dwValue, LPCTSTR lpszValueName); // 双字类型
LONG QueryValue(LPTSTR szValue, LPCTSTR lpszValueName, DWORD* pdwCount); // 字符串类型
LONG QueryValue(LPCTSTR pszValueName, DWORD* pdwType, void* pData, ULONG* pnBytes); // 任意类型
(4)设置、创建、删除键值数据。
LONG SetValue(DWORD dwValue, LPCTSTR lpszValueName); // 设置键值数据,如果不存在则创建之。
LONG SetValue(LPCTSTR lpszValue, LPCTSTR lpszValueName = NULL);
LONG DeleteValue (LPCTSTR lpszValue); // 删除指定键值项
(5)将注册表数据立即写入磁盘。
LONG Flush(); // Windows并不一定会将注册表数据写入磁盘,可能在几分钟之后才会写。
// 如果需要立即写入磁盘,则必须手动调用该函数。此函数对应的API函数是RegFlushKey