- 虽然登录是由许多实体文件所组成,程序设计师还是可以像处理单一资料库般的对它做存取资讯的动作。
- 由于被设计成一个层次化的样式,登录允许应用程序在它的结构配置下加上它所有拥有的组织。
- 登录支援多使用者,在实质上分为二个部份:其一为本地端机器设定(HKEY_LOCAL_MACHINE),其二为使用者设定(HKEY_USERS)。
- 登录提供了许可权与稽查的安全能力,它能够被应用在特定的机码上。
- 登录允许多种资料型别,包括Binary、DWORD、String以及Multistring。
在开始了解登录的细节前,我们应该先讨论二个在Microsoft Windows 2000上可用来浏览与修改登录档的工具程序,RegEdit.exe与RegEdt32.exe。RegEdit.exe基本上与Microsoft Windows 98上的登录工具程序相同,但是它缺少了Windows 2000登录中支援的安全性特色。RegEdt32.exe较少被使用,但是它拥有所有登录机码的安全性支援,也提供更多对登录内含值的操作能力。在很多情形下您可以使用这二个工具之一来浏览与修改登录档,但是在某些情况中,您会被限制使用RegEdt32.exe或是一些其他的工具。
系统登录结构
登录是一个有限制性的、可被分享的系统资源。它产生了一些礼仪(或者协定)与规则,并由系统严格地强制执行。在继续之前,简单地说明一下登录的结构。
就像之前所提到的,系统登录被安排在一个由机码与值所组成的阶层式架构中。在机码中可以包含一个任意机码(或子机码)与值的数字。子机码等于居民,可以包含一个它们所拥有的任意子机码与值之数字。机码的名称在它们同层级间同组的资料中必须唯一,而且不能包含反斜线符号。图5-1中显示了登录的结构。
登录拥有一个逻辑的结构与一个实体的结构。程序设计师通常只关心逻辑结构的部份。在逻辑上,登录包含了一些与驱动A: 类似的一些根机码,并且把驱动C: 视为根目录。一个登录的根机码表示它为登录树上的最高节点。实际上,无论如何登录皆被保存在使用者硬盘上的多个文件中,称为hives。程序设计师只需思考登录中的一个机码路径即可。在内部地处理上,作业系统会决定哪一些hive可包含这个机码,而系统则负责存取适当的文件。
图5-1 使用RegEdit.exe所显示的Windows 2000登录结构 |
Windows 2000之登录提供了五个事先定义的根机码与一个事先定义的执行效能资料机码(HKEY_PERFORMANCE_DATA),显示于表格5-1中。HKEY_LOCAL_MACHINE与HKEY_USERS机码为那些已存在于登录之子机码的根机码。这个在HKEY_LOCAL_MACHINE与HKEY_USERS下之登录的逻辑分割透过提出两个截然不同的需求而提出登录的目的:特定机器之设定资讯的储存以及特定使用者设定资讯的储存。
表格5-1 Windows 2000事先定义的系统登录机码 |
事先定义的机码名称 | 说明 |
---|---|
HKEY_LOCAL_MACHINE | 不管哪一个使用者登录,皆应用在本地端机器上的一组登录资料。 |
HKEY_USERS | 一组应用在特定使用者之登录资料。HKEY_USERS包含了预设使用者以及任何目前已载入之使用者设定档的 树状结构。 |
HKEY_CURRENT_USER | 一个在HKEY_USERS下的子树之系统定义书签或别名,可以动态地指向与使用者关联之呼叫线程的登录资讯。有特别关于呼叫线程的规则,使用者可以模拟服务的使用。 |
HKEY_CLASSES_ROOT | 一个系统为了HKEY_LOCAL_MACHINE/Software/Classes而定义的书签或别名。这个树状结构中存在着已注册的COM元件与在系统中关于Shell的相关设定 资讯。 |
HKEY_CURRENT_CONFIG | 一个提交至HKEY_LOCAL_MACHINE/System/ |
CurrentControlSet/Hardware Profiles/ | Current中的系统定义书签或别名。这个登录树状结构中存在着硬体设定资讯。 |
HKEY_PERFORMANCE_ DATA | 这个事先定义的机码参考到由系统提供的即时执行效能资料以及应用程序。在登录中,它不会被回复为实际的 值。更确切地说,登录函数提供了一个一致的方法以动 态地取回系统的执行效能资料。 |
以下叁个事先定义的登录机码是在HKEY_LOCAL_MACHINE与HKEY_USERS机码下方落下之阶层区段的虚拟书签。例如,在HKEY_LOCAL_MACHINE /Software/Classes下发现的子机码也可以被系统已定义的HKEY_CLASSES_ROOT机码存取。
如同子机码名称一样,在一个单一子机码下未命名的值,除了预设值以外,所有的值必须拥有唯一的名称。所有的值,包含预设值皆为非必须的。那就是说,一个机码可以包含一个值、很多值或是根本没有值。
说明
诸如系统元件的文件名称、机码名称、子机码名称以及值的名称在Windows的国际版本中并没有特定地位。当然,任何被储存在值里面的资料通常都会使用当地的文字转换。
每一个值皆有资料与它关联。一个值的资料被格式化为一个系统已定义的资料型别,在表格5-2中说明。本章后面将描述有关这些资料型别的一些使用与分派的内容。
表格5-2 登录值之资料型别 |
登录值之资料型别 | 说明 |
---|---|
REG_BINARY | 一个位元组资料流。 |
REG_DWORD | 一个32位元的数字。 |
REG_DWORD_LITTLE_ENDIAN | 使用little-endian格式的一个32位元数字。所有的Windows系统皆使用little-endian格式来储存数字。 |
REG_DWORD_BIG_ENDIAN | 一个使用big-endian格式之32位元数字。一些非Windows系统,例如一些执行UNIX与一个多样化的Motorola CPU的硬体皆使用big-endian格式来储存整数。 |
REG_QWORD | 一个64位元的数字。 |
REG_QWORD_LITTLE_ENDIAN | 一个使用little-endian格式之64位元数字。等于REG_QWORD。 |
REG_EXPAND_SZ | 一个以零终止的字串,包含了使用「%VARIABLE_NAME%」格式的未说明环境变数之参考。此型别通常被使用来储存文件路径,因为即使使用者正在调整他的系统路径,像「%SystemRoot%」的变数允许您的路径被正确地储存(本章后面之〈在系统登录中储存资料〉一节中将讨论如何使用这个型别的值)。 |
REG_LINK | 一个经由系统而被使用的Unicode symbolic link。您的应用程序或服务之程序代码应该不要求也不储存REG_LINK型别的值。 |
REG_MULTI_SZ | 一个以零终止字串的序列,与二个跟随在序列之最后字串后面的零字元。 |
REG_NONE | 没有被定义的值型别。它在功能上与REG_BINARY相同。 |
REG_RESOURCE_LIST | 一个设备驱动的资源清单。没有被使用在使用者模式之应用程序中。 |
REG_SZ | 一个以零终止的字串。 |
Microsoft定义了在存取登录时,程序设计师应该遵守的惯例,然而,作业系统无法执行这些惯例。例如,程序开发者常见的错误是在登录中属于特定使用者的部份放置特定机器的设定,反之亦然。
登录惯例
这一节讨论登录的用法及其惯例,特别集中在被设计用在Windows之伺服软件上。
透过系统加强的规则与规定之紧密结合,保持了登录的完整性。规定会使这个登录成为软件的一个通用资料库引擎,并帮助在此分享的系统资源避开不适当的混乱和交通。
您应该为您的软件储存架构资讯做出的第一个决定即:是哪一些资讯适用于机器(或所有使用者)或者仅适用于系统的特定使用者。以另一种方式来说,如果资讯对应到您软件中的一个特征时,不管与使用者相关的部份,您想要让将该特征设定成皆相同或者您会想让每一个使用者皆有属于自己的设定资讯呢?
在一般的情形中,当软件安装时设定为可应用于系统之所有使用者,该资讯会储存在登录中。这个处理程序很像安装硬体时在系统上的情形。相对地,在软件执行时改变使用者设定与结构通常会储存在一个特定使用者的设定中。
特定机器之登录设定
当一个应用程序(或服务)需要储存在机器上执行之特定结构资料时,规定会命令它来储存在这个层次中的机码与值:
HKEY_LOCAL_MACHINE Software Your Company Name Your Product Name Your Product Version (optional) Key1 Value1 Value2 ... Key2 ...
依据此结构可以避免登录的混乱并可以更容易的寻找您的特定软件资讯。通常伺服软件不会为特定使用者储存结构资讯。它为特定机器的结构;所以不需要或者没有机会在一个特定使用者的基础下调整它的行为。
特定使用者之登录设定
在HKEY_USERS下的子机码展示了使用者的结构资讯。系统会自动地将HKEY_CURRENT_USER对应到目前使用者之子机码上—即是与目前处理程序相关之使用者的子机码。如果有一个线程模拟了另一个使用者(关于模拟的部份会在第十一章中详细说明),则被模拟的使用者也会参考到经由该线程而参考HKEY_CURRENT_USER之所有内容。
如果您的软件将会存取特定使用者之登录资讯,则它应该遵循着一个与使用HKEY_LOCAL_MACHINE类似的规定:
HKEY_CURRENT_USER Software Your Company Name Your Product Name Your Product Version (optional) Key1 Value1 Value2 . . . Key2 . . .
当然,您可以经由HKEY_USERS根机码来直接存取这个阶层架构,但是并不建议如此做。如果您真的需要如此做时,您可以使用RegOpenCurrentUser函数。若要了解HKEY_CURRENT_USER与在HKEY_USERS下找到之资料相关的方法,包含Token以及使用者内容的部份皆在第十一章中详细讨论。
登录机码的操作
这一节会描述由系统实作的函数,可以用来存取系统登录的内容。在讨论这些特殊函数之前,先对二组函数做个区分:登录函数以及Shell登录函数。
登录与Shell登录函数
登录函数是一组基本的函数,它被系统所实作,用来存取系统登录内容。从Win32程序设计介面被建立时,这些函数就已经存在了,它存在于一个称为AdvAPI32.dll之DLL文件中。
只要能够使用登录做的任何事皆可以使用登录函数来完成。可以经由叁个前置字元Reg来描述它们—例如,RegOpenKeyEx。
Shell登录函数建立在这些「标准的」登录函数上,并且提供延伸或简化的功能。Shell登录函数不但被预期可以容易的存取登录,而且可以经由应用程序以更一致的使用方法来执行登录。这些函数只有在系统上已安装Microsoft Internet Explorer 4.0或更新的版本时才会有效。
为了使用Shell函数,您必须在您的原始程序代码中包含ShlWAPI.h标头档,也可以在您的连结器中选择ShlWAPI.lib文件。标准的登录函数不需要包含任何特殊的文件。
Shell函数是经由简单的参数清单与二个前置字元SH而描述的—例如SHCopyKey。
因为标准的登录函数提供了基础介面给登录,本章将集中于这部份。然而,偶尔也会在适当的时机提到Shell函数。
开启登录机码
在您可以从登录值中储存与检索前,您必须得到一个登录机码的handle。您可以使用二个基本的方式来取得该handle:开启一个已存在的登录机码或是建立一个新的机码。RegOpenKeyEx函数应该被使用来开启已存在的登录机码。它的函数原型如下:
LONG RegOpenKeyEx( HKEY hkeyRoot, PCTSTR pszSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult);
说明
RegOpenKey函数也可以被用来至登录机码中取得一个handle。然而,您应该总是使用登录函数之Ex版本,如果它存在的话。
为了开启一个子机码,您会将根机码的handle传递给hkeyRoot参数。该handle应该是您想要开启或是一些被要求之子机码的祖先节点之一。系统也允许您传递列在表格5-1中之任何事先已定义的机码值给hkeyRoot。通常,一个应用程序会至少会需要建立一个对RegOpenKeyEx的呼叫,并传递HKEY_LOCAL_ MACHINE或HKEY_CURRENT_USER给hkeyRoot参数。
pszSubKey参数是您想要开启之机码的本文名称。这个名称必须包含在所被要求的机码与根之间,并以反斜线字元分开。例如,如果您想要取得一个对HKEY_LOCAL_MACHINE/Software/Microsoft/Windows子机码具有读取权限的handle,您应该使用以下所示的方法呼叫RegOpenKeyEx:
HKEY hkeyWindows; LONG lRet =RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software//Microsoft//Windows"),NULL,KEY_READ,&hkeyWindows);
如果您正开启一个被指定根机码之目前子机码,则您不需在pszSubKey参数中加入父机码名称或反斜线符号,只需包含被要求机码的名称即可。若您传递一个空字串或一个NULL值给pszSubKey参数,系统会回传一个新的handle给被指定的根机码。当您需要一个不同于原始的根机码handle之安全存取时,请传递NULL给pszSubKey参数。
ulOptions参数目前没有被使用并且保留给未来需要时使用。现在,您应该总是传递0给此参数。
samDesired参数描述您的应用程序打算对被要求的登录机码做什么,而且它可以是任何在表格5-3中所示之标记组合。因为您的应用程序在存取一个特定的登录机码时可能会被限制为安全存取,所以请避免使用透过KEY_ALL_ACCESS传送的方法。只要求您的应用程序打算使用的方式呼叫RegOpenKeyEx,以增加成功的可能性。
表格5-3 可以被传递至RegOpenKeyEx的samDesired参数之标记 |
登录之存取标记 | 说明 |
---|---|
KEY_ALL_ACCESS | 结合KEY_QUERY_VALUE、KEY_ENUMERATE_ SUB_KEYS、KEY_NOTIFY、KEY_CREATE_SUB_ KEY、KEY_CREATE_LINK与KEY_SET_VALUE的存取。 |
KEY_CREATE_LINK | 允许建立一个symbolic link。 |
KEY_CREATE_SUB_KEY | 允许建立一个子机码。 |
KEY_ENUMERATE_SUB_KEYS | 允许列举子机码。 |
KEY_EXECUTE | 允许做读取之存取动作。 |
KEY_NOTIFY | 允许改变通告。 |
KEY_QUERY_VALUE | 允许询问子机码资料。 |
KEY_READ | 结合KEY_QUERY_VALUE、KEY_ENUMERATE _SUB_KEYS与KEY_NOTIFY的存取动作。 |
KEY_SET_VALUE | 允许设定子机码资料。 |
KEY_WRITE | 结合KEY_SET_VALUE与KEY_CREATE_SUB_KEY的存取动作。 |
您应该将一个HKEY变数的位址传递给phkResult参数。如果RegOpenKeyEx成功的话,系统便会使用开启的机码handle来填满这个变数。若它成功而且在该机码不存在时传回ERROR_FILE_NOT_FOUND,则RegOpenKeyEx函数会回传ERROR_SUCCESS。
说明
不像大部份其他的Windows函数,登录函数会回传错误码。您将不需要为了使一个登录函数取得更多的错误资讯而呼叫GetLastError。
建立登录机码
RegCreateKeyEx函数允许您去建立一个机码。您也可以使用它来取回一个机码的handle。不同于RegOpenKeyEx的是,如果欲存取的机码不存在时,RegCreateKeyEx不会自动地停止作用。它反而会使用您所提供的名称去试图建立一个新的机码。RegCreateKeyEx的函数原型如下:
LONG RegCreateKeyEx( HKEY hkeyRoot, PCTSTR pszSubKey, DWORD Reserved, PTSTR pszClass, DWORD dwOptions, REGSAM samDesired, PSECURITY_ATTRIBUTES psa, PHKEY phkResult, PDWORD pdwDisposition);
您将会从RegOpenKeyEx函数中确认hkeyRoot、pszSubKey、samDesired与phkResult参数。这些参数的作用与RegCreateKeyEx相同,但是有一个值得注意的例外情形是:如果在pszSubKey参数中指定的机码并不存在,那么系统会试图去建立一个新的子机码以完成此要求。
除了常见的参数外,RegCreateKeyEx还拥有一个与机码对应之类别名称且指向一个以零终止字串的pszClass参数。该类别名称是被保留的,而且您应该总是传递NULL给此参数。
dwOptions参数可以为表格5-4中的任何一个值。
psa参数指向一个SECURITY_ATTRIBUTES结构,允许您为新建立之子机码定义存取权限。如果子机码已经存在,则安全描述项(Security Descriptor)资讯会被忽略(请参阅
表格5-4 可以被传递至RegCreateKeyEx之dwOptions参数值 |
选项值 | 说明 |
---|---|
REG_OPTION_NON_VOLATILE | 机码被建立在登录内并且在持续地放置在系统硬盘上。 |
REG_OPTION_VOLATILE | 机码被储存在内存内,系统重开机后即不存在。 |
REG_OPTION_BACKUP_RESTORE | 机码经由使用备份软件备份并回存至登录中。 |
说明
典型的登录机码是具非挥发性(nonvolatile)的。当电脑重新开机时,挥发性(Volatile)资料通常会被删除。当您只是暂时性的需求而使用机码时,即可使用挥发性的机码以避免产生混乱的情形。挥发性的机码可以用来在处理程序间有效地做资料交换—因为登录是可以远端控制的,所以即使是在不同机器上之处理程序作业也可以做很好的通信(请参阅后面
〈远端地存取登录〉 一节所讨论的RegConnectRegistry函数内容)。
pdwDisposition参数指向一个DWORD变数,系统会填入以下二个之一的值: REG_CREATED_NEW_KEY或REG_OPENED_EXISTING_KEY。当函数回传并显示是否建立了一个子机码或简单地开启了已存在的子机码时,检查该变数的内容。大部份的时间里,您的软件不需要去知道实际上是否建立了这个子机码,因此,RegCreateKeyEx允许您传递NULL给这个最后的参数。
软件在设定资料时通常会使用RegCreateKeyEx去建立与开启它的机码,聪明的系统可能会立即为软件建立一个新的机码。RegCreateKeyEx提供一个便利的方法可以在一个登录机码被删除即为它重建所需的结构资讯与预设资料,那些好奇的管理员早已知道并常常做这些事。
不管您是否透过建立一个呼叫RegCreateKeyEx或RegOpenKeyEx的方式从已开启的登录机码处取回您的handle,当您完成了对它们的使用时,应该将已开启登录之handle传递给RegCloseKey:
LONG RegCloseKey(HKEY hkey);
列举登录机码
当软件已被安装或设定时,通常会知道被要求的登录机码名称。然而,在一些情况中,在一个已开启机码中的子机码名称可能就不会被知道。RegEnumKeyEx函数可以被使用来列举子机码。它的函数原型如下:
LONG RegEnumKeyEx( HKEY hkey, DWORD dwIndex, PTSTR pszName, PDWORD pcbName, PDWORD pdwReserved, PTSTR pszClass, PDWORD pcbClass, PFILETIME pftLastWriteTime);
因为您的程序代码必须提供缓冲区以存放被回传的类别名称与机码名称值,所以您的责任即是传递足够用来储存任何子机码与类别名称的缓冲区;否则此函数会失败。为了做到这点,您应该建立一个对RegQueryInfoKey的初始呼叫以取回位于所需机码下之最长子机码与类别名称的长度。因为一个新的子机码可能会在您的程序代码呼叫RegQueryInfoKey与RegEnumKeyEx之间被加入,所以您的程序代码还必须具有处理一个较长子机码名称之事件的能力。RegQueryInfoKey函数的原形如下:
LONG RegQueryInfoKey( HKEY hkey, PTSTR pszClass, PDWORD pcbClass, PDWORD pdwReserved, PDWORD pcSubKeys, PDWORD pcbMaxSubKeyLen, PDWORD pcbMaxClassLen, PDWORD pcValues, PDWORD pcbMaxValueNameLen, PDWORD pcbMaxValueLen, PDWORD pcbSecurityDescriptor, PFILETIME pftLastWriteTime);
以下所示的程序代码片段说明了在一个任意机码下如何列举子机码名称的方法:
VOID PrintSubKeyNames(HKEY hkey){ //取得最长机码名称的字元长度 DWORD dwMaxSubkeyLen; RegQueryInfoKey(hkey,NULL,NULL,NULL,NULL,&dwMaxSubkeyLen, NULL,NULL,NULL,NULL,NULL,NULL); //增加一个NULL terminator dwMaxSubkeyLen++; //配置子机码的缓冲区 PTSTR pszKeyName =(PTSTR)_alloca(dwMaxSubkeyLen *sizeof(TCHAR)); //储存缓冲区长度 DWORD dwKeyNameLen =dwMaxSubkeyLen; //从起点开始 DWORD dwIndex =0; FILETIME ftLastWritten; //循环执行,直到失败为止 while (ERROR_SUCCESS ==RegEnumKeyEx(hkey,dwIndex++,pszKeyName, &dwKeyNameLen,NULL,NULL,NULL,&ftLastWritten)){ //列印机码名称 _tprintf(TEXT("%s /n "),pszKeyName); //每一次皆回存缓冲区长度 dwKeyNameLen =dwMaxSubkeyLen; }}
说明
Shell登录函数包含了二个函数,与RegQueryInfoKey和RegEnumKeyEx的功能相似,定义如下:
DWORD SHQueryInfoKey( HKEY hkey, PDWORD pcSubKeys, PDWORD pcchMaxSubKeyLen, PDWORD pcValues, PDWORD pcchMaxValueNameLen);
与
DWORD SHEnumKeyEx( HKEY hkey, DWORD dwIndex, PTSTR pszName, PDWORD pcchName);
请注意因为不要求使用不常用的参数,所以Shell登录函数使用起来更简单。
用登录值工作
为了这个主题,我们已经讨论过大部份的登录值,而没有提到太多如何使用登录值工作的部份。登录值实际上就像是登录的主要部份,它储存了您的应用程序之实际结构资料。一旦您由一个开启的登录机码处取得一个有效的handle时,即表示您已经准备好可以取回包含在机码中的资料。
叁个从系统登录中取回值的函数:RegQueryValueEx、RegEnumValue与RegQueryMultipleValues。现在先集中于RegQueryValueEx与RegEnumValu的部份。而RegQueryMultipleValues的部份则在〈远端地存取登录〉一节中讨论。
当您不知道包含您想取回之资料值的名称,或者您想要有系统地取回所有包含在一个特定机码内的资料值时,您应该使用RegEnumValue函数。反之,您应该使用RegQueryValueEx在一个机码内从一个单一值中获得资料,其名称(或在预设值的情形下,缺少一个名称)之前已知。RegQueryValueEx函数原型如下:
LONG RegQueryValueEx( HKEY hkey, PTSTR pszValueName, PDWORD pdwReserved, PDWOR pdwType, PBYTE pbData, PDWORD pcbData);
hkey参数是一个对开启的KEY_QUERY_VALUE存取的handle,而pszValueName是您想要取回值的名称。如果您传递NULL或一个指向空字串的值给pszValueName参数,则系统会从机码的预设值中取回资料。
pdwType参数指向一个DWORD变数,系统将会填入列在表格5-2之资料型别值(例如REG_SZ或REG_BINARY)之一的值,以指示登录值的资料型别。
pbData参数指向一个函数以登录值的资料填满的缓冲区,而pcbData指向一个包含被传递之缓冲区大小的DWORD。在函数由pcbData使用包含在登录值中以位元组为单位之资料大小指向DWORD的情形下,您也可以传递NULL给pbData参数。
说明
如果您的缓冲区大小无法储存从登录值取回的所有资料,则RegQueryValueEx会回传ERROR_MORE_DATA错误。在所有情形下,您的应用程序应该能够小心地处理这个错误。通常回应是要去配置一个较大的缓冲区和再一次执行对函数的呼叫。因为一些其他的处理程序可能会在您的应用程序检查它的资料大小后改变它,所以在呼叫RegQueryValueEx前检查登录值资料的大小并不保证一定会执行成功。
CautoBuf类别包含在随书光碟中,它提供了一个很好的方式去处理要求不同大小之资料缓冲区函数。这个C++ 类别被频繁地使用在本书的范例应用程序中。
通常如果一个应用程序将要取回一个单一的登录值,它会建立二个对RegQueryValueEx的呼叫。用这个初始呼叫来重新得到缓冲区所需的大小。在配置适当大小的缓冲区后,第二个被建立之对RegQueryValueEx的呼叫会真的取回资料。
然而,有第二个根据您将要读取之主要登录值数字的方法可能会更有效:建立一个单一的RegQueryInfoKey呼叫,以回传包含在机码中最大值的大小。然后您的应用程序可以配置一个被使用在多个呼叫至RegQueryValueEx的单一缓冲区。
说明
Shell登录函数实作了一个从登录值中取回资料的函数,称为SHGetValue。定义如下:
DWORD SHGetValue( HKEY hkey, PCTSTR pszSubKey, PCTSTR pszValue, PDWORD pdwType, PVOID pvData, PDWORD pcbData);
请注意此函数非常类似RegQueryValueEx,但是它也包含了一个pszSubKey参数以允许您去指定包含登录值的机码名称。这使得您的应用程序不用再负起呼叫RegOpenKeyEx与RegCloseKey的责任。
如果您不知道询问中的登录值名称,或是您想要在一个单一的机码中取回每一个包含在内的资料,那么您应该使用RegEnumValue,它的函数原型如下:
LONG RegEnumValue( HKEY hkey, DWORD dwIndex, PTSTR pszValueName, PDWORD pcbValueName, PDWORD pdwReserved, PDWORD pdwType, PBYTE pbData, PDWORD pcbData);
就像您所看到的,RegEnumValue与RegQueryValueEx非常相似,除了它不是传递一个登录值名称给RegEnumValue外,您会传递一个被hkey参考且与登录值顺序位置相关之机码的索引值。 RegEnumValue会回传在缓冲区内经由pszValueName参数所指的登录值名称。
为了找到适当的缓冲区大小,您可以取回登录值的名称,您的应用程序可以建立一个对RegQueryValueEx的初始呼叫,并指定NULL给pbData参数。如此做可以取回机码内最长的登录值名称。
RegScan范例应用程序
RegScan范例应用程序(「05 RegScan.exe」)显示在列表5-1中,说明了如何列举机码与在一个被指定机码下的值,还有如何从被列举的值中取回资料的方法。它的原始程序代码与资源文件可在随书光碟中的05-RegScan目录中找到。这个范例应用程序会以递回的方式在登录中来回移动,就像它在一个机码名称或一个登录值内搜寻一个特定字串一样。
当您执行了RegScan时,您可以键入想要搜寻登录的机器名称。然后您可以选择一个根机码和一个启动中的子机码并且在String编辑控制项中键入一个搜寻字串。图5-2显示了我的搜寻结果。在本章后面的章节中(〈远端地存取登录〉一节中),将会讨论如何使RegScan搜寻一个远端机器中的登录。
图5-2 一个使用RegScan范例应用程序的搜寻 |
RegScan.cpp/********************************************************************模组:RegScan.cpp通告:Copyright (c)2000 Jeffrey Richter********************************************************************/#include "../CmnHdr.h " //请参阅附录A#include <WindowsX.h>#include <stdio.h>#include "Resource.h"#define UILAYOUT_IMPL#include "../ClassLib /UILayout.h " //请参阅附录B#define PRINTBUF_IMPL#include "../ClassLib /PrintBuf.h " //请参阅附录B#define AUTOBUF_IMPL#include "../ClassLib /AutoBuf.h " //请参阅附录B#define REGWALK_IMPL#include "RegWalk.h"///class CRegScan :private CRegWalk {public: CRegScan():m_pb(256 *1024){} BOOL Go(PCTSTR pszMachine,HKEY hkeyRoot,PCTSTR pszSubkey, PCTSTR pszString,BOOL fSearchKeyNames,BOOL fSearchValueNames, BOOL fSearchValueData,BOOL fCaseSensitive); PCTSTR Result(){return(m_pb);} void ForceSearchStop(){m_fStopSearch =TRUE;} BOOL WasSearchStopped(){return(m_fStopSearch);}private: PCTSTR m_pszString; //要搜寻的字串 BOOL m_fSearchKeyNames; //要搜寻机码名称? BOOL m_fSearchValueNames; //要搜寻登录值名称? BOOL m_fSearchValueData; //要搜寻登录值之字串资料? BOOL m_fShownThisSubkey; //任何符合现行子机码的? BOOL m_fStopSearch; //提早停止搜寻? CPrintBuf m_pb;//Growable results buffer typedef PTSTR (WINAPI*PFNSTRCMP)(PCTSTR pszFirst,PCTSTR pszSearch); PFNSTRCMP m_pfnStrCmp;//String comparison functionprotected: REGWALKSTATUS onSubkey(PCTSTR pszSubkey,int nDepth, BOOL fRecurseRequested); REGWALKSTATUS onValue(HKEY hkey,PCTSTR pszValue,int nDepth); void ProcessUI();};///BOOL CRegScan::Go(PCTSTR pszMachine,HKEY hkeyRoot,PCTSTR pszSubkey, PCTSTR pszString,BOOL fSearchKeyNames,BOOL fSearchValueNames, BOOL fSearchValueData,BOOL fCaseSensitive){ m_pszString =pszString; m_fSearchKeyNames =fSearchKeyNames; m_fSearchValueNames =fSearchValueNames; m_fSearchValueData =fSearchValueData; m_pfnStrCmp =fCaseSensitive ?StrStr :StrStrI; m_fShownThisSubkey =FALSE; m_fStopSearch =FALSE; m_pb.Clear(); BOOL fOk =TRUE; if (!m_fSearchKeyNames &&!m_fSearchValueNames &&!m_fSearchValueData){ chMB("You must at least select one field to search."); }else fOk =CRegWalk::Go(pszMachine,hkeyRoot,pszSubkey,TRUE); return(fOk);}///void CRegScan::ProcessUI(){ MSG msg; while (PeekMessage(&msg,0,0,0,PM_REMOVE)){ //这里是讯息的使用者介面,处理它们 if (!IsDialogMessage(GetActiveWindow(),&msg)){ TranslateMessage(&msg); DispatchMessage(&msg); } }}///CRegWalk::REGWALKSTATUS CRegScan::onSubkey(PCTSTR pszSubkey,int nDepth, BOOL fRecurseRequested){ REGWALKSTATUS rws =RWS_FULLSTOP; if (fRecurseRequested ||(nDepth ==0))rws =RWS_RECURSE; //不使用完整路径即取得子机码的名称 PCTSTR pszSubkeyName =PathFindFileName(pszSubkey); if (m_fSearchKeyNames) m_fShownThisSubkey =(m_pfnStrCmp(pszSubkeyName,m_pszString)!=NULL); else m_fShownThisSubkey =FALSE; if (m_fShownThisSubkey){ m_pb.Print(TEXT("%s /r /n "),pszSubkey); } ProcessUI(); return(WasSearchStopped()?RWS_FULLSTOP :rws);}///CRegWalk::REGWALKSTATUS CRegScan::onValue( HKEY hkey,PCTSTR pszValue,int nDepth){ if (m_fSearchValueNames &&(m_pfnStrCmp(pszValue,m_pszString)!=NULL)){ if (!m_fShownThisSubkey){ m_pb.Print(TEXT("%s /r /n "),m_szSubkeyPath); m_fShownThisSubkey =TRUE; } m_pb.Print(TEXT("/t%s /r /n "),pszValue); } if (m_fSearchValueData){ //检查登录值的资料 DWORD dwType; RegQueryValueEx(hkey,pszValue,NULL,&dwType,NULL,NULL); if ((dwType ==REG_EXPAND_SZ)||(dwType ==REG_SZ)){ CAutoBuf<TCHAR,sizeof(TCHAR)>szData; //给一个大于0的缓冲区以便RegQueryValueEx返回 //ERROR_MORE_DATA代替ERROR_SUCCESS szData =1; while (RegQueryValueEx(hkey,pszValue,NULL,NULL,szData,szData) ==ERROR_MORE_DATA); //szData为NULL表示这里没有登录值资料 if (((PCTSTR)szData !=NULL)&& (m_pfnStrCmp(szData,m_pszString)!=NULL)){ if (!m_fShownThisSubkey){ m_pb.Print(TEXT("%s /r /n "),m_szSubkeyPath); m_fShownThisSubkey =TRUE; } m_pb.Print(TEXT("/t%s (%s)/r /n "), ((pszValue [0 ] ===0)?TEXT("(default)"):pszValue), (PCTSTR)szData); } } } ProcessUI(); return(WasSearchStopped()?RWS_FULLSTOP :RWS_CONTINUE);}///CUILayout g_UILayout; //当对话方块大小改变时,负责控制///BOOL Dlg_OnInitDialog(HWND hwnd,HWND hwndFocus,LPARAM lParam){ chSETDLGICONS(hwnd,IDI_REGSCAN); HWND hwndRootKey =GetDlgItem(hwnd,IDC_ROOTKEY); int n =0; n =ComboBox_AddString(hwndRootKey,TEXT("HKEY_LOCAL_MACHINE ")); ComboBox_SetItemData(hwndRootKey,n,HKEY_LOCAL_MACHINE); ComboBox_SetCurSel(hwndRootKey,n); //HKLM为预设值 n =ComboBox_AddString(hwndRootKey,TEXT("HKEY_CURRENT_CONFIG ")); ComboBox_SetItemData(hwndRootKey,n,HKEY_CURRENT_CONFIG); n =ComboBox_AddString(hwndRootKey,TEXT("HKEY_CLASSES_ROOT ")); ComboBox_SetItemData(hwndRootKey,n,HKEY_CLASSES_ROOT); n =ComboBox_AddString(hwndRootKey,TEXT("HKEY_USERS ")); ComboBox_SetItemData(hwndRootKey,n,HKEY_USERS); n =ComboBox_AddString(hwndRootKey,TEXT("HKEY_CURRENT_USER ")); ComboBox_SetItemData(hwndRootKey,n,HKEY_CURRENT_USER); //重新建立控制的大小 g_UILayout.Initialize(hwnd); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_TOPRIGHT, IDC_MACHINE,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_TOPRIGHT, IDC_ROOTKEY,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_TOPRIGHT, IDC_SUBKEY,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_TOPRIGHT, IDC_STRING,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPRIGHT,CUILayout::AP_TOPRIGHT, IDC_SEARCHKEYNAMES,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPRIGHT,CUILayout::AP_TOPRIGHT, IDC_SEARCHVALUENAMES,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPRIGHT,CUILayout::AP_TOPRIGHT, IDC_SEARCHVALUEDATA,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPRIGHT,CUILayout::AP_TOPRIGHT, IDC_CASESENSITIVE,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPRIGHT,CUILayout::AP_TOPRIGHT, IDC_SEARCHSTART,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPRIGHT,CUILayout::AP_TOPRIGHT, IDC_SEARCHSTOP,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_BOTTOMRIGHT, IDC_SEARCHRESULTS,FALSE); CheckDlgButton(hwnd,IDC_SEARCHKEYNAMES,TRUE); CheckDlgButton(hwnd,IDC_SEARCHVALUENAMES,TRUE); CheckDlgButton(hwnd,IDC_SEARCHVALUEDATA,TRUE); return(TRUE);}///void EnableControls(HWND hwnd,BOOL fEnable){ EnableWindow(GetDlgItem(hwnd,IDC_MACHINE),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_ROOTKEY),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_SUBKEY),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_STRING),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_SEARCHKEYNAMES),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_SEARCHVALUENAMES),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_SEARCHVALUEDATA),fEnable); EnableWindow(GetDlgItem(hwnd,IDC_CASESENSITIVE),fEnable); ShowWindow(GetDlgItem(hwnd,IDC_SEARCHSTART),fEnable ?SW_SHOW :SW_HIDE); ShowWindow(GetDlgItem(hwnd,IDC_SEARCHSTOP),fEnable ?SW_HIDE :SW_SHOW);}///void Dlg_OnCommand(HWND hwnd,int id,HWND hwndCtl,UINT codeNotify){ static CRegScan x; switch (id){ case IDCANCEL: EndDialog(hwnd,id); break; case IDC_SEARCHSTOP: x.ForceSearchStop(); break; case IDC_SEARCHSTART: SetDlgItemText(hwnd,IDC_SEARCHRESULTS,TEXT("Scanning Registry...")); EnableControls(hwnd,FALSE); TCHAR szString [1000 ]; GetDlgItemText(hwnd,IDC_STRING,szString,chDIMOF(szString)); TCHAR szMachine [100 ],szSubkey [1000 ]; GetDlgItemText(hwnd,IDC_MACHINE,szMachine,chDIMOF(szMachine)); GetDlgItemText(hwnd,IDC_SUBKEY,szSubkey,chDIMOF(szSubkey)); HWND hwndRootKey =GetDlgItem(hwnd,IDC_ROOTKEY); int nIndex =ComboBox_GetCurSel(hwndRootKey); HKEY hkeyRoot =(HKEY)ComboBox_GetItemData(hwndRootKey,nIndex); if (!x.Go( (szMachine [0 ] ==0)?NULL :szMachine,hkeyRoot,szSubkey, szString, IsDlgButtonChecked(hwnd,IDC_SEARCHKEYNAMES), IsDlgButtonChecked(hwnd,IDC_SEARCHVALUENAMES), IsDlgButtonChecked(hwnd,IDC_SEARCHVALUEDATA), IsDlgButtonChecked(hwnd,IDC_CASESENSITIVE))){ chMB("Couldn 't access the registry "); } SetDlgItemText(hwnd,IDC_SEARCHRESULTS, x.WasSearchStopped()?TEXT("Scan Canceled "): ((x.Result()[0 ] ===0)?TEXT("No entries found "):x.Result())); EnableControls(hwnd,TRUE); break; }}///void Dlg_OnSize(HWND hwnd,UINT state,int cx,int cy){ //改变子控制的位置 g_UILayout.AdjustControls(cx,cy);}///void Dlg_OnGetMinMaxInfo(HWND hwnd,PMINMAXINFO pMinMaxInfo){ //回传对话方块的最小尺寸 g_UILayout.HandleMinMax(pMinMaxInfo);}///INT_PTR WINAPI Dlg_Proc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){ switch (uMsg){ chHANDLE_DLGMSG(hwnd,WM_INITDIALOG,Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd,WM_COMMAND,Dlg_OnCommand); chHANDLE_DLGMSG(hwnd,WM_SIZE,Dlg_OnSize); chHANDLE_DLGMSG(hwnd,WM_GETMINMAXINFO,Dlg_OnGetMinMaxInfo); } return(FALSE);}///int WINAPI _tWinMain(HINSTANCE hinstExe,HINSTANCE,PTSTR pszCmdLine,int){ DialogBox(hinstExe,MAKEINTRESOURCE(IDD_REGSCAN),NULL,Dlg_Proc); return(0);}End of File //
RegWalk.h/********************************************************************模组:RegWalk.h通告:Copyright (c)2000 Jeffrey Richter********************************************************************/#pragma once //每一次编译单元时即包含此标头档///#include <ShlWapi.h>///class CRegWalk {public: CRegWalk(){} virtual ~CRegWalk(){} BOOL Go(PCTSTR pszMachine,HKEY hkeyRoot,PCTSTR pszSubkey,BOOL fRecurse); enum REGWALKSTATUS {RWS_FULLSTOP,RWS_CONTINUE,RWS_RECURSE };protected: virtual REGWALKSTATUS onSubkey(PCTSTR pszSubkey,int nDepth, BOOL fRecurseRequested); virtual REGWALKSTATUS onValue(HKEY hkey,PCTSTR pszValue,int nDepth);protected: HKEY m_hkeyRootMachine; //机器上的根机码 BOOL m_fRecurse; //递回至子机码中? int m_nDepth; //递回深度 TCHAR m_szSubkeyPath [MAX_PATH ]; //子机码路径private: REGWALKSTATUS RegWalkRecurse(); REGWALKSTATUS EnumValuesInSubkey();};///#ifdef REGWALK_IMPL///#pragma comment(lib,"shlwapi ")///CRegWalk::REGWALKSTATUS CRegWalk::onSubkey(PCTSTR pszSubkey,int nDepth, BOOL fRecurseRequested){ return(fRecurseRequested ?RWS_RECURSE :RWS_CONTINUE);}CRegWalk::REGWALKSTATUS CRegWalk::onValue(HKEY hkey,PCTSTR pszValue, int nDepth){ return(RWS_CONTINUE);}///CRegWalk::REGWALKSTATUS CRegWalk::EnumValuesInSubkey(){ HKEY hkey =NULL; REGWALKSTATUS rws =RWS_CONTINUE; if (ERROR_SUCCESS ==RegOpenKeyEx(m_hkeyRootMachine,m_szSubkeyPath,0, KEY_QUERY_VALUE,&hkey)){ for (int nIndex =0;rws !=RWS_FULLSTOP;nIndex++){ TCHAR szValueName [256 ]; //没有超过255个字元的登录值名称 DWORD cbValueName =chDIMOF(szValueName); if (ERROR_SUCCESS !=RegEnumValue(hkey,nIndex, szValueName,&cbValueName,NULL,NULL,NULL,NULL)) break; rws =onValue(hkey,szValueName,m_nDepth); } chVERIFY(RegCloseKey(hkey)==ERROR_SUCCESS); } return(rws);}///CRegWalk::REGWALKSTATUS CRegWalk::RegWalkRecurse(){ //回报此机码 REGWALKSTATUS rws =onSubkey(m_szSubkeyPath,++m_nDepth,m_fRecurse); //列举在子机码中的登录值? if (rws ==RWS_RECURSE)rws =EnumValuesInSubkey(); //继续列举子机码? if (rws !=RWS_FULLSTOP){ HKEY hkey =NULL; if (ERROR_SUCCESS == RegOpenKeyEx(m_hkeyRootMachine,m_szSubkeyPath,0, KEY_ENUMERATE_SUB_KEYS,&hkey)){ for (int nIndex =0;rws !=RWS_FULLSTOP;nIndex++){ TCHAR szSubkeyName [256 ]; //没有超过255个字元的子机码名称 DWORD cbSubkeyName =chDIMOF(szSubkeyName); if (ERROR_SUCCESS !=RegEnumKeyEx(hkey,nIndex, szSubkeyName,&cbSubkeyName,NULL,NULL,NULL,NULL)) break; //新增子机码至路径中 if (m_szSubkeyPath [0] !!=0)StrCat(m_szSubkeyPath,TEXT("//"));StrCat(m_szSubkeyPath,szSubkeyName);rws =RegWalkRecurse(); //从路径中截断最后的子机码 PTSTR p =StrRChr(m_szSubkeyPath,NULL,TEXT('//')); if (p !=NULL)*p =0; else m_szSubkeyPath [0 ] =0; } chVERIFY(RegCloseKey(hkey)==ERROR_SUCCESS); } } m_nDepth--; return(rws);}///BOOL CRegWalk::Go(PCTSTR pszMachine,HKEY hkeyRoot,PCTSTR pszSubkey, BOOL fRecurse){ //nDepth指示了从上至下共有多少阶级 m_nDepth =-1; m_fRecurse =fRecurse; m_hkeyRootMachine =NULL; REGWALKSTATUS rws =RWS_FULLSTOP; __try { if (ERROR_SUCCESS != RegConnectRegistry(pszMachine,hkeyRoot,&m_hkeyRootMachine)) __leave; lstrcpy(m_szSubkeyPath,pszSubkey); //呼叫递回函数开始沿着子机码走 rws =RegWalkRecurse(); } __finally { if (m_hkeyRootMachine !=NULL) RegCloseKey(m_hkeyRootMachine); } return(rws !=RWS_FULLSTOP);}///#endif //REGWALK_IMPLEnd of File //
列表5-1 RegScan范例应用程序 |
在系统登录中储存资料
在讨论如何在系统登录中储存资料前,让我们先复习关于登录的适当用法之主题。如您所知,系统登录是一个被分享的资源。就其本身而言,它是受限制并且必须经由序列化(serialized)来存取它。您的应用程序可以在登录中储存许多MB之不同资料型别资料,但是在您的应用程序里最好不要将这些能力用到最大。
当在考虑登录的储存时,您应该问您自己二个问题:
- 这个资料是要储存我的应用程序之结构资讯部份,或者是一般的应用程序资料?(例如,它会不适当的将一个文字处理器的文件储在登录中)。
- 我所储存之资料数量大到(而且足够我经常存取此资料)能使得资料储存在文件中比储存在登录里更有效吗?(例如,一个经由文字处理器之应用程序所使用的拼字检查字典若是储存在文件里是比较好的)。
在产生您的决定时,您会在文件系统的杂乱与在系统登录中无效率地储存大量的资料间权衡其利弊。后续的〈有效地使用系统登录〉一节中将更进一步讨论有效的使用系统登录部份。
您可以使用RegSetValueEx函数去储存资料至登录中。以下是它的函数原型:
LONG RegSetValueEx( HKEY hkey, PCTSTR pszValueName, DWORD dwReserved, DWORD dwType, CONST BYTE *pbData, DWORD cbData);
hkey参数参考到被储存之登录值里的机码。pszValueName是您想要使用的登录值之名称。如果您传递NULL或是一个指向空字串的指标给pszValueName,系统会在机码的预设登录值中储存您的资料。dwType参数应该依据您在此登录值中储存的资料型别而被设定为表格5-2所列之资料型别值之一。pbData 参数指向在登录中实际被储存的资料。在cbData参数中,您应以位元组为单位传递资料的大小。
如果RegSetValueEx执行成功,它会回传ERROR_SUCCESS。以下的程序代码说明了经由一个称为RegSetStringValue之函数包装RegSetValueEx的使用方法:
LONG RegSetStringValue(HKEY hkey,PCTSTR pszValueName, PCTSTR pszString){ //取得字串的长度 int nDataSize =lstrlen(pszString); //加入一个结束字元的位置 nDataSize++; //依字元大小相乘 nDataSize =nDataSize *sizeof(TCHAR); //试图去储存资料至登录中 return(RegSetValueEx(hkey,pszValueName,0,REG_SZ, (PBYTE)pszString,nDataSize));}
说明
Shell登录函数包含了一个称为SHSetValue的函数,与SHGetValue一样,在一开始不要求您的应用程序从一个机码中取得handle。SHSetValue函数被定义如下:
DWORD SHSetValue( HKEY hkey, PCTSTR pszSubKey, PCTSTR pszValue, PDWORD pdwType, PVOID pvData, PDWORD pcbData);
如表格5-2所示,登录支援了许多资料型别,您可以依您的应用程序所需而选择型别。您将会发现自己经常使用REG_DWORD、REG_SZ与REG_BINARY。一个应用程序会使用REG_DWORD来储存数字的设定,用REG_SZ(或REG_MULTI_SZ)来储存文字设定,而REG_BINARY则储存其他没有相符的登录型别之资料。当在使用REG_BINARY型别时,一般的登录工具像RegEdt32,并不知道如何去展示除了位元组区块之登录值以外的资料。
在继续之前,我想要澄清二个可能会令人困惑的登录型别:当REG_EXPAND_ SZ和REG_MULTI_SZ. The REG_EXPAND_SZ。在它是一个被储存在登录中的零终止Unicode或ANSI字串的情形下,REG_EXPAND_SZ登录型别会与REG_SZ型别很类似。它们的不同之处是,在惯例上通常会在将储存于REG_EXPAND_SZ的值从登录取出后再传递给ExpandEnvironmentStrings函数。
说明
登录函数不会自动地展开REG_EXPAND_SZ型别之登录值中的资料。您的应用程序应负起传递该字串至ExpandEnvironment Strings的责任。
这里是ExpandEnvironmentStrings的函数原型:
DWORD ExpandEnvironmentStrings( PCTSTR pszSrc, PTSTR pszDst, DWORD nSize);
因为文件中的说明使得REG_MULTI_SZ被大大地误解。文件声明一个REG_MULTI_SZ值是「一个被二个零字元终止之零终止字串阵列」。我认为此文件应该被解读成:「一个REG_MULTI_SZ值以一个连续的零终止字串之形式而被储存,并有二个零字元终止了最后的字串」。记得当您在储存或取回此型别的资料时,您的缓冲区大小必须也包含了所有用于终止的零字元,并在最后多加上一个零。图5-3显示了一个由叁个字串组成之多零终止字串(multizero-terminated)登录值的范例。
图5-3 一个由叁个字串组成的多零终止字串登录值 |
范例中的资料显示了叁个字「Jason's」、「Test」以及「MULTI_SZ」,它们就像是个别分开的字串一样。缓冲区将会被要求以ANSI形式保留23个位元组以及以Unicode的形式保留46个位元组给此资料。
远端存取登录
Windows允许您在一个远端的机器上使用与您在本端机器存取登录相同的方法去存取登录的内容。这使得在开发一个可以从网路上的远端位置设定软件的工作变得更容易。
说明
为了考虑直接在Windows下给予远端的登录存取,需要钻研至安全的部份。我将安全性部份的讨论内容延后到第十章再详细说明。
Windows登录函数的设计者实际上已经由允许应用程序开发者使用与他们用来存取本机登录相同的函数来简化远端存取登录的工作。例如,您可以使用RegOpenKeyEx去开启在远端机器与本地端机器上的机码。
同样地,RegSetValueEx、RegQueryValueEx、RegEnumKey等函数皆可被用来操作一个远端机器上的登录。唯一的不同是取得对远端机器上的系统事先定义登录机码之一的handle。这可以经由呼叫RegConnectRegistry来完成,它的函数原型如下:
LONG RegConnectRegistry( PTSTR pszMachineName, HKEY hkey, PHKEY phkResult);
如您所见,RegConnectRegistry函数相当容易。传递想要连接的机器名称至pszMachineName参数中,您也可以传递一个事先定义之登录机码(例如HKEY_LOCAL_MACHINE或HKEY_USERS)以指示您想要存取的远端机器之登录部份。最后,您要提供RegConnectRegistry一个在储存一个开启的handle至远端登录的根机码函数内指向HKEY变数的指标。
说明
在您的应用程序中可以传递NULL给pszMachineName参数以指示一个与本地端机器的连结。这是在使用最小的附加逻辑而建立在远端或本地工作之登录工具时的一个方便机制。
为了从一个远端登录中取回登录值,您可以使用RegQueryMultipleValues函数。此函数与RegQueryValueEx类似,只是它可以要求由许多部份组成的值。RegQueryMultipleValues函数允许您的应用程序经由一个单一的网路操作而要求由许多部份组成的值,以将网路流量最小化。RegQueryMultipleValues并不是专门用在远端登录的操作中,只是它实际上较常被使用在此而已。在十分需要网路频?的企业中,每一个网路的碰撞皆是成本的一部份。以下为RegQueryMultipleValues的函数原型:
LONG RegQueryMultipleValues( HKEY hkey, PVALENT val_list, DWORD num_vals, PTSTR pszValueBuf, PDWORD dwTotsize);
您的应用程序必须经由val_list参数来传递一个VALENT结构的阵列至函数中。系统接着会使用此阵列来寻找被取回的值,以及存放在包含每一个登录值资讯的结构。以下为VALENT结构的宣告内容:
typedef struct value_ent { PTSTR ve_valuename; DWORD ve_valuelen; DWORD ve_valueptr; DWORD ve_type; }VALENT;
它很奇特,登录函数的设计者也不会去依照单一操作的方法而实作一个在登录内设定多个值的函数。因为登录通常被读取的机会比被写入多,所以我假设它并没有被完成。当然,您可以经由建立多个对RegSetValueEx的呼叫而实作属于您自己的「RegSe-MultipleValues」。然而,使用这个函数的实作并不会减少网路流量。
说明
RegQueryMultipleValues函数被限制每一次呼叫皆不能读取超过1MB的资料。只有在它能回传所有被要求的值时,该函数才会执行成功。如果被要求的资料皆超过1MB,它会失败。
有效地使用系统登录
现在您已经知道让您在系统登录中操作资料的特征,所以您可以开始学习如何在您的服务应用程序里建立这些特征。登录应该充当您的服务结构资讯之贮藏处,但是在某些方面,它也会充当一个让管理者用来把结构需求通知服务器的一个机制。
大部份的服务器上皆已实作了许多服务,而这些服务没有可显示的使用者介面。事实上,不管用来发展一个服务器的处理程序为何,它们皆有充份的理由可以把一个使用者介面从您的服务器排除。当您排除了使用者介面时,您通常会建立另外一个容易管理的应用程序来设定您的服务。此应用程序可以在登录中储存那些可被您的服务读取的结构资讯。管理的应用程序应该能在远端或本地执行,而且也能够在Microsoft Windows 95或Window 98上执行。
当您在设计您的服务与管理应用程序时,您应该依照一些重要的规则以建立一个最有效之使用系统登录方法。
- 您的服务器应该尽可能地不与登录接触 。 系统登录是一个被分享的资源,而且必须被序列化后才能存取,由系统来处理此序列。每次您呼叫一个登录函数时,该呼叫线程便要承担等待另一个线程正在存取登录的风险。这可以严重地影响一个经常建立呼叫登录的应用程序。在您的服务器应该在启动时便从登录读取结构,并且在经由管理服务器的软件上随意地重新读取结构资讯。大部份的服务会要求停止服务,然后再使用新的结构参数重新启动它,然而这并不是必需的。
- 您的服务器应该不要对登录的改变做询问的动作 。 系统提供一个在登录被改变时可以通知服务的方法(在下一节〈登录改变的通知〉中将会讨论)。您的服务应该使用此特征为一个侦测结构改变的机制而非对系统登录执行一个周期性的询问动作。
- 避免经由登录而使它回报事件执行效能的资料 。 您的管理应用程序与服务应该避免回报事件或执行效能的资料至非挥发性的登录机码中。反之应用程序与服务应该使用透过具挥发性的HKEY_PERFORMANCE_DATA登录机码来实作的效能监视器功能。请参阅
登录改变的通知
另一个更有效地使用登录的方法是利用登录改变的通知。一个应用程序可以有效地在经由使用RegNotifyChangeKeyValue函数而改变登录机码内容时被通知。这个函数允许您去告诉系统在什么型别改变时收到通知以及在任意一个子机码被改变时被通知。以下为函数的原型:
LONG RegNotifyChangeKeyValue( HKEY hkey, BOOL fWatchSubtree, DWORD dwNotifyFilter, HANDLE hEvent, BOOL fAsynchronous);
hkey参数指示您想要取回的机码改变通知。fWatchSubtree参数则指示了您想要收到在被指定之机码下整个树内是否有发生改变的通知。dwNotifyFilter参数为您想要在哪些登录被改变时收到通知。dwNotifyFilter参数则可以为任何列示在表格5-5中的标记组合。
表格5-5 可以被传递到RegNotifyChangeKeyValue之dwNotifyFilter参数的标记 |
通知标记 | 说明 |
---|---|
REG_NOTIFY_CHANGE_NAME | 当有一个子机码被加入或删除时,通知会出现。 |
REG_NOTIFY_CHANGE_ATTRIBUTES | 当机码的属性改变时,包含安全控制项资讯,通知会出现。 |
REG_NOTIFY_CHANGE_LAST_SET | 当机码的值被改变时,包含该值的附加或删除,通知会出现。 |
REG_NOTIFY_CHANGE_SECURITY | 当机码的安全控制项改变时,通知会出现。 |
可以把RegNotifyChangeKeyValue函数用于二种方法之一:同步地或非同步地。在同步地使用函数时,在fAsynchronous中传递FALSE值。在同步的呼叫RegNotifyChangeKeyValue时,呼叫线程会被搁置,直到有一个通知出现为止。非同步地呼叫函数会使函数立即返回。当下一个登录发生改变时(符合被指定的筛选条件),系统会自动地发出事件核心物件的信号。
因为在处理程序中没有浪费线程,所以建议使用RegNotifyChangeKeyValue的非同步特征。请记住不论您是否要使用非同步的函数,在每一个对RegNotifyChangeKeyValue的呼叫只适用于一个单一的通知。为了在将来可以收到通知,该函数必须被多次地呼叫。
说明
如果被RegNotifyChangeKeyValue呼叫的线程已终止,系统会删除为了通知的要求。这通常不是问题,因为一般等待事件的线程与被RegNotifyChangeKeyValue呼叫的线程相同。 如果在一个改变通知发生前,被RegNotifyChangeKeyValue呼叫的线程即已终止,那么系统会发出一个事件讯号并释放任何正在等待事件的线程。
说明
要在不用建立一个机码改变的情形下,将同步呼叫至RegNotifyChangeKeyValue的线程返回,您的应用程序必须关闭对登录机码的handle。
RegNotify范例应用程序
RegNotify范例应用程序(「05 RegNotify.exe」),显示在列表5-2中,说明了非同步RegNotifyChangeKeyValue函数的使用方法。它的原始程序代码与资源文件皆放在随书光碟的05-RegNotify目录中。此程序简单地监视了一个被使用者指定之登录机码的通知产生情形。
当RegNotify被执行后,使用者可以键入一个想要自动取回的登录机码。然后使用者按下Watch按钮即开始被指定之登录机码的通知处理程序。图5-4显示了在一个新的登录值被加入机码后,RegNotify范例应用程序执行的情形。
图5-4 在叁个新的值被加入至KLM/SOFTWARE/RegNotify后,RegNotify范例应用程序显示的对话方块 |
通知处理程序的工作就像这样:RegNotify应用程序建立一个为被选择的机码而多次呼叫RegNotifyChangeKeyValue回圈之附加线程,并使它显示在唯读之编辑控制项中的登录值资讯。每一次发生通知时,事件的讯号会被发出,并且会使WaitForSingleObjectEx回传WAIT_OBJECT_0的值。一个WAIT_OBJECT_0值的回传是因为该函数为了另一个通知而再执行回圈一次。
当使用者关闭应用程序时,主要的线程便会发出一个使用者模式之非同?程序呼叫(Asynchronous Procedure Call,APC)给通知线程,这会导致WaitForSingleObjectEx因为WAIT_IO_COMPLETION而离开。
说明
把APC用作线程之间通信的一个形式有时是理想的,因为这么做可能会更有效。它不需要创造一个核心物件,例如一个信号或一个事件。当您使用为了线程之间通信的APC时,常常必须建立仅为一个APC预留位置的函数外没有做任何事的APC回呼函数。
如果您过去从来不曾知道这个技术,那么一开始您可能会被这个空的预留位置函数所混淆。请参阅
列表5-2 RegNotify范例应用程序 |
RegNotify.cpp/********************************************************************模组:RegNotify.cpp通告:Copyright (c)2000 Jeffrey Richter********************************************************************/#include "../CmnHdr.h " //请参阅附录A#include <WindowsX.h>#include <Process.h> //为了_beginthreadex而包含此档#include "Resource.h"#define UILAYOUT_IMPL#include "../ClassLib /UILayout.h " //请参阅附录B///CUILayout g_UILayout; //当对话方块大小改变时,负责做控制/我们使用此函数来取代一个事件,目的是让线程知道离开主要回圈//的时间已到void WINAPI DoNothingAPC(ULONG_PTR dwParam){}///DWORD WINAPI RegSubkeyWatcher(PVOID pv){ HWND hwnd =(HWND)pv; //为了通知而建立我们的事件 HANDLE hEvent =CreateEvent(NULL,FALSE,FALSE,NULL); TCHAR szSubkey [200 ]; //取得我们将要建立或为了察看而开启的登录机码 GetDlgItemText(hwnd,IDC_REGKEY,szSubkey,chDIMOF(szSubkey)); //取得我们的登录机码以察看 HKEY hkey =NULL; RegCreateKeyEx(HKEY_LOCAL_MACHINE,szSubkey,0,NULL, REG_OPTION_NON_VOLATILE,KEY_NOTIFY |KEY_QUERY_VALUE, NULL,&hkey,NULL); do { DWORD dwIndex =0,cbValName; BYTE bData [1024 ]; TCHAR szValName [100 ]; cbValName =chDIMOF(szValName); DWORD dwType,cbData =chDIMOF(bData); TCHAR szRegVals [20 *1024 ] =={0 }; //RegEnumValue列举在机码中的值 while (ERROR_SUCCESS ==RegEnumValue(hkey, dwIndex++,szValName,(cbValName =chDIMOF(szValName),&cbValName), NULL,&dwType,bData,(cbData =chDIMOF(bData),&cbData))){ PTSTR p =szRegVals +lstrlen(szRegVals); wsprintf(p,TEXT("/r /n%s /t "),szValName); p =szRegVals +lstrlen(szRegVals); //处理不同的型别 switch (dwType){ case REG_DWORD: wsprintf(p,TEXT("0x%08x "),*(PDWORD)bData); break; case REG_EXPAND_SZ: case REG_LINK: case REG_MULTI_SZ: case REG_RESOURCE_LIST: case REG_SZ: wsprintf(p,TEXT("%s "),bData); break; case REG_NONE: case REG_DWORD_BIG_ENDIAN: default: wsprintf(p,TEXT("Unknown type ")); break; case REG_BINARY: for (DWORD x =0;x <cbData;x++) wsprintf(p +lstrlen(p),TEXT("%02X "),bData [x ]); break; } } //显示设定 SetDlgItemText(hwnd,IDC_REGVALUES,&szRegVals [2 ]); //略过"/r /n" //设定通知…注意我们每一次皆必须做它 //线程建立一个对{RegNotifyChangeKeyValue}的呼叫以成为在事件 //上等待的一员,所以它也是很重要的 RegNotifyChangeKeyValue(hkey,FALSE, REG_NOTIFY_CHANGE_NAME |REG_NOTIFY_CHANGE_ATTRIBUTES | REG_NOTIFY_CHANGE_LAST_SET |REG_NOTIFY_CHANGE_SECURITY, hEvent,TRUE); //永远等待事件或APC}while (WaitForSingleObjectEx(hEvent,INFINITE,TRUE)==WAIT_OBJECT_0); //我们处理了事件与机码We are done with the event and key CloseHandle(hEvent); RegCloseKey(hkey); return(0);}///BOOL Dlg_OnInitDialog(HWND hwnd,HWND hwndFocus,LPARAM lParam){ chSETDLGICONS(hwnd,IDI_REGNOTIFY); //重新设定控制的大小 g_UILayout.Initialize(hwnd); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_BOTTOMRIGHT, IDC_REGVALUES,FALSE); g_UILayout.AnchorControl(CUILayout::AP_TOPLEFT,CUILayout::AP_TOPRIGHT, IDC_REGKEY,FALSE); SetDlgItemText(hwnd,IDC_REGKEY,TEXT("SOFTWARE //RegNotify ")); return(TRUE);}///void Dlg_OnCommand(HWND hwnd,int id,HWND hwndCtl,UINT codeNotify){ static HANDLE s_hThread =NULL; switch (id){ case IDCANCEL: if (s_hThread !=NULL){ //将无用的APC函数放入伫列中,以发出我们的另一个线程离开 //讯号 QueueUserAPC(DoNothingAPC,s_hThread,0); //等待线程离开 WaitForSingleObject(s_hThread,INFINITE); CloseHandle(s_hThread); s_hThread =NULL; } EndDialog(hwnd,id); break; case IDOK: //使我们的按钮失效 EnableWindow(hwndCtl,FALSE); //启动通知线程 s_hThread =chBEGINTHREADEX(NULL,0,RegSubkeyWatcher,hwnd,0,NULL); break; }}///void Dlg_OnSize(HWND hwnd,UINT state,int cx,int cy){ //负责子控制项的处理 g_UILayout.AdjustControls(cx,cy);}///void Dlg_OnGetMinMaxInfo(HWND hwnd,PMINMAXINFO pMinMaxInfo){ //回传对话方块之最小尺寸 g_UILayout.HandleMinMax(pMinMaxInfo);}///INT_PTR WINAPI Dlg_Proc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){ switch (uMsg){ chHANDLE_DLGMSG(hwnd,WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd,WM_COMMAND, Dlg_OnCommand); chHANDLE_DLGMSG(hwnd,WM_SIZE, Dlg_OnSize); chHANDLE_DLGMSG(hwnd,WM_GETMINMAXINFO, Dlg_OnGetMinMaxInfo);} return(FALSE);}///int WINAPI _tWinMain(HINSTANCE hinstExe,HINSTANCE,LPTSTR pszCmdLine,int){ DialogBox(hinstExe,MAKEINTRESOURCE(IDD_REGNOTIFY),NULL,Dlg_Proc); return(0);}End of File //
维护一个乾净的登录
如本章曾提到许多次的,避免登录的紊乱的责任要依靠应用程序开发者来达成。您的伺服程序(或任何您编写的其他应用程序)应该不只是尝试去建立有效使用系统的程序,也要在它被完成时清理它。
系统提供了二个可以从登录中删除资料的函数:RegDeleteKey与RegDeleteValue。它们的函数原型如下:
LONG RegDeleteValue( HKEY hkey, PCTSTR pszValueName);LONG RegDeleteKey( HKEY hkey, PCTSTR pszSubKey);
您可能会惊讶的发现有关删除资料并非一个完全微不足道的主题。假设您的应用程序对删除一个特定资料拥有安全的权限,那么RegDeleteValue函数就像它看起来那样简单。然而,您的应用程序没有删除一个机码之权限的可能性会迫使开发者在以下的主要限制条件中实作RegDeleteKey:RegDeleteKey只会删除一个没有包含子机码的机码。稍微思考一下即可使这个限制的原因变得较清楚—您的处理程序可能不允许删除位于您想要删除之机码下的子机码。系统无法简单地沿着树状结构移动,以及在开始执行删除程序时确认安全性的权限,因为如此做可能会变成一个冗长的作业,而且在安全性确认通过以后,该权限可能会允许它可以在子机码做真正的改变动作。这样的轮流可能会使对子机码的删除动作在删除操作中失败,并留下一棵部分被毁坏的登录树。
尽管RegDeleteKey不会删除一个包含子机码的机码,Windows Shell的开发者已经实作的一个类似RegDeleteKey函数则可以做到。此函数称为SHDeleteKey,并定义如下:
DWORD SHDeleteKey( HKEY hkey, PCTSTR pszSubKey);
在呼叫SHDeleteKey前,您的处理程序应该要拥有删除所有基本机码的权限。如果您的处理程序没有这个权限,那么在它呼叫SHDeleteKey时,该函数会侦测到此情形,并回传「拒绝存取」。注意到SHDeleteKey与之前所提的那些遭遇竞赛条件的情形相同,如果在呼叫至SHDeleteKey的期间改变了对机码的权限,那么它可以离开并留下一棵部份被删除的树。
更多的登录函数
Windows 2000的设计者加入了一个已知的RegOverridePredefKey登录函数,它允许您的程序代码将一个列在表格5-1之事先定义的登录机码对应到另一个登录机码。 RegOverridePredefKey的预期目标是将透过一个软件元件(例如ActiveX控制项或是一个DLL)改变的登录重新导向至一个临时的机码。这样一来可以允许您的应用程序轻易且安全地决定必须在登录中添加什么机码和登录值以适应此元件。如果需要的话,在复制它们到登录中的预定位置前,您的应用程序可以执行改变或调整这些机码与登录值的动作。RegOverridePredefKey的函数原型如下:
LONG RegOverridePredefKey( HKEY hkey HKEY hNewKey);
您应该从表格5-1中选择一个事先定义的登录机码并传递给hkey参数,并且还需传递一个已存在的登录机码给hNewKey参数。若在hNewKey参数中传递NULL值会导致事先已定义的机码回复到它的预设对应状态。
最后,若没有提到RegSaveKey与RegRestoreKey便无法结束此章的内容。RegSaveKey允许您复制一个登录机码与它的基本树到一个文件中。而RegRestoreKey则允许您从一个文件回存登录机码。它们的函数原型列示如下:
LONG RegSaveKey( HKEY hkey, PCTSTR pszFile, PSECURITY_ATTRIBUTES psa);LONG RegRestoreKey( HKEY hkey, PCTSTR pszFile, DWORD dwFlags);
尽管就其本身而言这些函数稍微有点用处,然而它们与RegLoadKey和RegUnloadKey以及处理登录的储存有密切的关系,而这些内容皆包含在《Platform SDK》文件中。
第二章 的内容,以便了解更多关于这个技术与其他的线程间通讯机制内容。 第十章 ,以取得更多关于安全性与登录的资讯)。