注册表编程技术

Some Tips for Registry Programming

作者:osmose(ph4nt0m)

来源:幻影旅团(http://www.360docs.net/doc/info-67a92e35f111f18583d05a17.html)&&补天网(http://www.360docs.net/doc/info-67a92e35f111f18583d05a17.html)EMAIL:osmose@http://www.360docs.net/doc/info-67a92e35f111f18583d05a17.html

2003.7.18

注册表编程技术

写在前面的话

不敢给这篇文章起个太大的名字,毕竟这只是我这一段时间学习心得的一点总结,顶多仅仅是一些tips而已。其实我什么都不懂,所以我尽量写的详细,希望大家不要厌烦。

注册表编程其实并不是一件困难的事情,如果看一点材料,查一查msdn,本可以很快掌握。我在网上搜索了一下,发现很多高手提供的只是MFC的一个关于注册表的类,都很泛泛,真正的代码还需要自己添加。对于像我一样急功近利,希望一口吃成胖子的菜鸟们,如果很快上手是大家最关心的问题。说实话,我没有找到一个比较详细的说明,所以我开始写这篇文章,手边有的就是一个API函数表和MSDN。在此感谢CSDN上各位大牛(Skt32(Skt32),shilong(星矢の诗龙),

firela(firela),laolaoliu2002(老刘)等)对我的指点,那里也许是一个对程序爱好的人最应该去的地方之一。

也感谢幻影旅团的各位弟兄的帮助,那是一个只关心技术的地方。或许这些知识都很浅显,但是我们在学习。

这篇文章最初的目的是写出一些属于自己的程序,或许适合热衷于开启别人机器某个服务的人,学会编写一些程序操作注册表,也许就不用总等着拾人牙慧了。随着点滴的积累,逐渐觉得有更多的东西可以深挖或者涵括,于是就有了这篇整理的总结。

我从自己学习的角度,对遇到的困难尽可能的分析,给出结果。但是文中仍然遗留了一些问题无法解决。如果您有什么建议,欢迎指正。

BTW:文中的程序如无特殊说明,都是支持MFC的。新建项目时请注意。

第一章概述

先让我们活动一下脑筋,想想看要修改或者保存注册表,有哪些途径?

1.在windows下面打开运行窗口(按住win键和r键),输入regedit,OK,在FILE菜单里有导入导出,保存什么,修改什么,不多说了。

2.到古老的DOS方式,让我们重新幼稚一下。(下面的内容前人已经写过多次,抄过来用一下,像前辈们致敬!)

在DOS提示符下键入Regedit命令,将出现一个帮助屏幕。此屏幕给出了其命令行参数及其使用方法。

语法:Regedit[/L:system][/R:user]filename1

Regedit[/L:system][/R:user]/C filename2

Regedit[/L:system][/R:user]/E filename3[regpath]

其中:

/L:system指定system.dat文件的存放位置。

/R:user指定user.dat文件的存放位置。

filename1指定引入注册表数据库的文件名。

/C filename2指定形成注册表数据库的文件名。

/E filename3指定导出注册表文件的文件名。

regpath指定导出注册表文件的开始关键字(缺省为全部关键字)

现举几个例子说明regedit.exe在DOS下的使用方法。

【例1】将系统注册表数据库registry导出到reg1.reg文件中。

regedit/E reg1.reg

【例2】reg1.reg形成系统注册表数据库registry(全部)中。

regedit/C reg1.reg

【例3】将reg.dat引入系统注册表数据库中(部分)。

regedit reg.dat

【例4】将CJH开始的关键字导出注册表数据库,并命名为cjh.reg。

regedit/E cjh.reg cjh

【例5】指定system/dat存放在D:\PWIN中和user.dat存放在E:\PWIN中,将reg.dat数据文件形成一个新的注册表数据库registry。

regedit/L:D:\PWIN/R:E:\PWIN/C reg.dat

DOS下的手段当然不止这些,如果想体验程序的感觉,还需要麻烦你看看批处理:

@echo off

path=c:\windows;c:\windows\command;c:\dos

cls

echo正在导出注册表……

regedit/E txt.reg HKEY_CLASSES_ROOT\txtfile

echo.

echo注册表导出完毕!按任一键开始编辑注册表……

echo.

pause

edit txt.reg

echo正在将修改后的注册表导入……

regedit txt.reg

echo恭喜您!在MS-DOS方式下成功修改了注册表!

pause

cls

@echo on

把上面这段代码复制到一个.bat文件中,你可以用EDIT这个命令编辑。从某种意义上说,充分发挥EDIT编辑器的强大功能,我们可以在遵循导出的注册表文件的格式的前提下,对注册表进行随心所欲的修改、删除或者增加任一子键。如果觉得这还不够程序化,您可以发挥DOS环境下各种程序设计语言的优势,加上交互性的界面,将这一过程真正的程序化,应该丝毫不亚于Windows 状态下的利用API函数做出来的效果。

3.在C程序中调用regedit命令,不要忘记C程序可以调用DOS命令的哟。记得加入#include DOS.h

这种方法和上面一种比起来换汤不换药,不多说了。

3.MFC有专门操作注册表的库,据说很方便,我没有用过,因为我不会MFC,也许只是那几个类吧,也许有很多。没做过的东西,就不能说它简单。如果哪位高人研究过,请补充一下。

4.调用API函数。API函数是个好东西,VB,Delphi,BCB,什么都可以用它。有人建议最后学API,因为最难。是的,可是我急功近利,只学那几个注册表的函数,试试看能不能学到点什么。好了,上面是关于修改注册表的一些方法。下面让我们来看看注册表函数都有哪些。

键管理类RegCloseKey()RegCreateKey()RegCreateKeyEx()RegDeleteKey() RegDeleteKeyEx()RegOpenKey()RegOpenKeyEx()值管理类RegDeleteValue()RegQueryValue()RegQueryValueEx()RegSetValue() RegSetValueEx()

查询计数类RegQueryInfoKey()RegEnumKey()RegEnumKeyEx()RegEnumValue()备份/恢复类RegLoadKey()RegReplaceKey()RegRestoreKey()RegSaveKey()实用类RegConnectRegistry()RegNotifyChangeKeyValue()RegUnloadKey()安全类

RegGetKeySecurity()RegSetKeySecurity()

(仅适用于NT)

我会选其中的一部分作具体介绍,这个文章能写到什么地步,能不能全部写完,我也不知道,看大家的反映了。

大家都知道,2001年微软停止开发win98系统内核,专攻NT,上面的一些函数还有专门服务于win3.1的,够古老的吧。比如,RegSetValueEx()和RegSetValue()有什么不同呢?前者可用于基于win32系统的应用开发。RegSetValue()则是服务于win3.1系统的。使用的时候要注意:Windows NT:HKEY_PERFORMANCE_DATA

Windows95and Windows98:HKEY_DYN_DATA

这两个子键是不一样的,虽然其他的都一样(HKEY_CLASSES_ROOT,

EY_CURRENT_CONFIG,HKEY_CURRENT_USER,HKEY_LOCAL_MACHINE,

HKEY_USERS)

不要误会,这两个函数都可以用在95,98系统上,具体什么差别呢,看看MSDN的话:Win32-based applications should use the RegSetValueEx function,which allows you to set any number of named

values of any data type.(基于win32的应用程序推荐使用RegSetValueEx函数,可以对设置任意类型任意键名任意值。。。)所以,以后有带Ex的函数,我们尽量都用它啦。

OK,第一部分先写到这里。第二部分,我会给出一个完整的例子,如何保存注册表里任意一个键和他的所有子键,里面涉及到一些权限的问题,调用了一些API函数获得操作注册表的权限。第三部分,引用并且分析MSDN中的一个例子,通过注册表列举函数保存HKEY_LOCAL_MACHINE 这样一个大键。第四部分是关于修改注册表键值,也有一个具体的例子,同时可能会谈到一点创建子键的问题,希望让大家都能看得轻松一点。我会结合MSDN中的解释尽量把每一个函数讲清楚,至少是我遇到的问题都说清楚。

第二章保存注册表的特定键

这一部分,我们来看看如何保存注册表里任意一个键以及它的所有子键。所需要用到的函数是RegOpenKeyEx,RegSaveKey和RegCloseKey。

看他们的名字,你也可以看出来他们是做什么用的。

RegOpenKeyEx负责打开指定的键。在后文,也许我们会用到另一个函数打开某个特定的键:RegCreateKeyEx。这里先把他们做一个比较:

这两个函数都适用于基于win32的应用程序,都可以打开指定子键。所不同的是,当指定子键不存在时,RegCreateKeyEx会自动生成这个键,而RegOpenKeyEx仅仅是负责打开。如果指定键不存在,RegOpenKeyEx返回失败信号。下面,让我们看看具体的操作:

LONG RegOpenKeyEx(

HKEY hKey,//用于打开键的句柄

LPCTSTR lpSubKey,//储存打开子键名称的变量的地址

DWORD ulOptions,//保留值

REGSAM samDesired,//访问形式

PHKEY phkResult//句柄的地址

);

这个函数是一个long型函数,如果执行成功,会返回ERROR_SUCCESS。

为了大家看得明白,我把每个变量都解释一下,磨刀不误砍柴工。

HKEY hKey:这个句柄其实就是下面几个东西里的一个,打开注册表,大家对他们都不陌生吧。

HKEY_CLASSES_ROOT

HKEY_CURRENT_CONFIG

HKEY_CURRENT_USER

HKEY_LOCAL_MACHINE

HKEY_USERS

Windows NT:HKEY_PERFORMANCE_DATA

Windows95and Windows98:HKEY_DYN_DATA

LPCTSTR lpSubKey:LPCTSTR是一个指向字符串常量的一个32位指针类型,字符可以是Unicode和DBCS。lpSubKey里可以放指针,也可以用变量,就像本文所用的。

DWORD ulOptions:这个没什么好说的,保留值,永远都是0,如果你想要继续干活的话。

REGSAM samDesired:访问形式,可以有下面几个值:

Value Meaning

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 Permission to create a symbolic link.(我不太明

白)

KEY_CREATE_SUB_KEY可以生成子键

KEY_ENUMERATE_SUB_KEYS可以枚举子键

KEY_EXECUTE可读

KEY_NOTIFY Permission for change notification.(也不太明

白,没用过)

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.

PHKEY phkResult:函数生成的指向打开键的一个句柄,后面的RegSaveKey函数就要用到它。使用完毕后,我们需要用RegCloseKey回收。

解释完了RegOpenKeyEx函数,让我们看看如何回收句柄RegCloseKey:

LONG RegCloseKey(

HKEY hKey//需要回收的句柄,就是上文的“PHKEY phkResult”

);

如果函数执行成功,返回值也是ERROR_SUCCESS。另外一个回收句柄的函数是RegFlushKey。退出前,RegFlushKey会往注册表里写一些信息,这个步骤可能会需要好几秒,机器需要完成一个从内存写入硬盘的过程,同时,这个函数会占用大量的系统资源,非必要时不要使用。RegCloseKey则是一个退出迅速的家伙,不会拖泥带水,“我们一直都用它”:D

现在你可能对打开一个键和关闭一个键都有点了解了,下面是操作的重点,打起精神来吧:)

LONG RegSaveKey(

HKEY hKey,//所要备份键的句柄

LPCTSTR lpFile,//储存文件的指针

LPSECURITY_ATTRIBUTES//生成文件的安全权限

);

HKEY hKey:就是我们用RegOpenKeyEx得到的那个新句柄。

LPCTSTR lpFile:这个函数把一个键和他的所有子键存储到一个文件中,这里的lpFile代表文件名的变量的地址,不过你也可以直接把文件名放在这里,我会show给你看的。LPSECURITY_ATTRIBUTES:这个东西说来话长,MSDN专门开了一页解释

SECURITY_ATTRIBUTES这个东西,感兴趣的朋友可以自己看看,我们这里用得很简单,NULL 就可以了。使用NULL意味着生成文件遵从默认的安全准则。

好了,呼出一口气,枯燥的教条终于完了。也许你已经跃跃欲试了。好,就让我们写一个简单的程序。

测试条件:VC6.0,WindowXP Prefessional,登陆账号:管理员(这个条件后面会提到)

#include"stdafx.h"

#include"windows.h"

#include"stdio.h"

#include"stdlib.h"

void main()

{

CString strKey="Software\\Microsoft\\Internet Explorer\\Main";

LPTSTR szSaveFileName;

HKEY hResult;

szSaveFileName=LPTSTR("1.dat");//要保存的文件名

RegOpenKeyEx(

HKEY_CURRENT_USER,

(LPCTSTR)strKey,

0,

KEY_ALL_ACCESS,

&hResult);//获得句柄

RegSaveKey(hResult,szSaveFileName,NULL);//保存键值

RegCloseKey(hResult);//释放句柄

}

没错就是这么简单,如果你仔细阅读了上面的讲解,看懂这一段一定没有问题。编译通过,没有问题。以后就可以这样照葫芦画瓢了。你要做的只是把

CString strKey="Software\\Microsoft\\Internet Explorer\\Main"和RegOpenKeyEx里的“HKEY_CURRENT_USER”换成你要保存的键就可以了。

运行吧,你会看到当前目录下生成了一个1.dat文件。成功了?仔细看看文件大小。0K?!!怎么回事?一定是哪里出了错误。我们一共就调用了三个函数,是哪一个出错了呢?加入一些语句调试一下。

#define ERROR111

#define ERROR222

#define ERROR333

#define SUCCESS44

if(RegCreateKeyEx(

HKEY_LOCAL_MACHINE,

(LPCTSTR)strKey,

0,

NULL,

REG_OPTION_NON_VOLATILE,

KEY_CREATE_SUB_KEY|KEY_WRITE|KEY_READ,

NULL,

&Result,

NULL)!=ERROR_SUCCESS)

{

cout<<"Error Opening Register...\n";

return ERROR1;

}

if(RegSaveKey(Result,szSaveFileName,NULL)!=ERROR_SUCCESS)

{

cout<<"Error saving Register...\n";

return ERROR2;

}

RegCloseKey(Result);

return SUCCESS;

注意,这里我换用了RegCreateKeyEx这个函数,只是想show一下这个函数的用法。大家可以跳过去,或者依然是用RegOpenKeyEx。最后发现,在调用RegSvaeKey的时候出错了。所有的这一些都是按照MSDN上的指示一步步来的,语法也没有问题,那会是哪里的原因呢?

我在RegSaveKey后面使用了一段测试代码,发现运行的时候,系统提示没有权限备份注册表。请注意,我可是用管理员的身份登陆XP的……真正的原因我们还是要去权限里找(在MSDN里搜索Windows NT Privileges,可以看到相关的东西)。这里简单说一下,在NT/2K/XP下失败的原因是没有SE_BACKUP_NAME权限。我们需要加入下面一段代码(基本功能是开一个线程申请权限,具体的恕不多介绍了,以后大家只要直接加入就可以了)。

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToke n))

return;

LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid);

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

好了,大家辛苦了。完整的程序如下:

#include"stdafx.h"//请千万不要忘记

#include"windows.h"//这两个头文件

#include"stdio.h"

#include"stdlib.h"

void main()

{

CString strKey="Software\\Microsoft\\Internet Explorer\\Main";

LPTSTR szSaveFileName;

HKEY hResult;

//申请备份权限

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToke n))

return;

LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid);

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

//开始备份工作

szSaveFileName=LPTSTR("1.dat");

RegOpenKeyEx(HKEY_CURRENT_USER,(LPCTSTR)strKey,0,KEY_ALL_ACCESS,&hResul t);

RegSaveKey(hResult,szSaveFileName,NULL);

RegCloseKey(hResult);

}

好了,到目前为止,我们已经学会了如何备份一个子键。可是如何备份整个HKEY_CURRENT_USER乃至于整个注册表呢?这是我们第三部分的内容。下一讲我们将会学到如何枚举所有的子键,并且结合MSDN上的一个例子,给大家做一个分析,同时也对这一讲的内容作一个复习。

Thank you so much for reading this article.

===============================================================

附录:

测试出错的代码:

long t=RegSaveKey(Result,szSaveFileName,NULL);

{

LPVOID lpMsgBuf;

FormatMessage(

FORMAT_MESSAGE_ALLOCATE_BUFFER|

FORMAT_MESSAGE_FROM_SYSTEM|

FORMAT_MESSAGE_IGNORE_INSERTS,

NULL,

t,

MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),//Default language

(LPTSTR)&lpMsgBuf,

0,

NULL);

AfxMessageBox((LPCTSTR)lpMsgBuf);

LocalFree(lpMsgBuf);

}

第三章保存整个注册表

PART I一个MSDN上的例子

上次我们学习了如何保存一个子键以及其所有从属子键。不过,你有没有想过要备份整个

HKEY_LOCAL_MACHINE这个根键或者整个注册表五个根键呢?上一次的方法如果你试试看,就会发现不适用了,呵呵。今天我打算结合MSDN上的一个例子谈一谈远程备份注册表的问题。学会远程备份,结合下一次的内容(修改注册表),大家可能自己就能够写程序远程修改注册表了。今天我们先学习一个新的函数,顺便把上次的内容温习一下。新函数是RegEnumKeyEx,注册表枚举函数。注意,这个函数带“Ex”的,那么肯定有RegEnumKey函数了。这两者的区别前面已经说过了,这里不再赘述。不过需要补充的是,和RegEnumKey不一样,RegEnumKeyEx除了返回子键名,还会返回最后一次修改这个子键的时间。你想到了什么?是不是有可能自己做一个类似文件系统检测的注册表检测的东东了?不过这个不是我们现在讨论的重点,让我们把基础知识看一看。

LONG RegEnumKeyEx(

HKEY hKey,//所要枚举子键的“母键”的句柄(这么说是为了理解方便)DWORD dwIndex,//要枚举的子键的目录

LPTSTR lpName,//指向存放子键名称的buffer地址的指针

LPDWORD lpcbName,//指向存放上面buffer size的地址的指针

LPDWORD lpReserved,//保留值

LPTSTR lpClass,//当函数返回时,指向一个buffer地址的指针。这个buffer

//保存了所枚举子键的类

LPDWORD lpcbClass,//存放buffer size(这个size是以字符的形式出现的)的指

//针,这个size包括了代表字符结束的NULL字符。

PFILETIME lpftLastWriteTime

//最后一次修改该键的时间

);

还是逐个的给点tips:

HKEY hKey:这个句柄可以是指向一个已经打开的键,也可以是那几个大键

(HKEY_CLASSES_ROOT,HKEY_CURRENT_CONFIG,HKEY_CURRENT_USER,

HKEY_LOCAL_MACHINE,HKEY_USERS,

Windows NT:HKEY_PERFORMANCE_DATA

Windows95and Windows98:HKEY_DYN_DATA)。

DWORD dwIndex:看起来很玄的东西。不要害怕,如果你在程序里是第一次调用这个函数时,你只需要把它设成0即可。以后每次调用都给它加1。MSDN上特地提醒大家,由于子键的顺序不是固定的,所以任何一个键都有可能有一个随机的子键目录。也就是说函数的返回的子键可能是随机排列的。

LPTSTR lpName:指针,指向存放子键名的buffer,包括那个代表结束的NULL字符,只保存键名

LPDWORD lpcbName:这个size也包括了代表字符结束的NULL字符。

LPDWORD lpReserved:没什么好说的,NULL。

LPTSTR lpClass:如果不需要知道class的时候,也应该写作NULL。

LPDWORD lpcbClass:只有当上面的lpclass为NULL的时候,这个才可置为NULL。PFILETIME lpftLastWriteTime:指向存放时间的指针,最后一次修改该键的时间

写到了这里,忽然很不爽。因为MSDN的例子使用了这个函数保存一个根键

HKEY_LOCAL_MACHINE,可是RegSaveKey也有保存所有子键的作用,我一直怀疑可以不用这个枚举函数就能搞定保存注册表。

好了,不管怎么样,我们来看看MSDN给我们的例子――远程保存注册表。

/*Save HKEY_LOCAL_MACHINE registry key,each subkey saved to a file of *name subkey

*

*this allows us to get around security restrictions which prevent

*the use of RegSaveKey()on the root key

*

*the optional target machine name is specified in argv[1]

*

*v1.21

*Scott Field(sfield)01-Apr-1995

*/

#define RTN_OK0

#define RTN_USAGE1

#define RTN_ERROR13

#include

#include

#include

//声明子函数

LONG SaveRegistrySubKey(

HKEY hKey,

LPTSTR szSubKey,

LPTSTR szSaveFileName);

void PERR(LPTSTR szAPI,DWORD dwLastError);

//主程序

int main(int argc,char*argv[])

{

TOKEN_PRIVILEGES tp;

HANDLE hToken;

LUID luid;

LONG rc;//储存Regxxx()函数返回值的变量

HKEY hKey;//定义句柄

LPTSTR MachineName=NULL;//指针,指向机器名

DWORD dwSubKeyIndex=0;//子键目录索引变量

char szSubKey[_MAX_FNAME];//这里用_MAX_FNAME建立一个动态数组

//比较适合存储子键名称

DWORD dwSubKeyLength=_MAX_FNAME;//子键buffer的长度

if(argc!=2)//usage

{

fprintf(stderr,"Usage:%s[]\n",argv[0]);

return RTN_USAGE;

}

//读入机器名

//set MachineName==argv[1],if appropriate

if(argc==2)MachineName=argv[1];

//

//设置备份注册表的权限,大体和上次我们用的那段代码差不多

//

if(!OpenProcessToken(GetCurrentProcess(),

TOKEN_ADJUST_PRIVILEGES,

&hToken))

{

PERR("OpenProcessToken",GetLastError());

return RTN_ERROR;

}

if(!LookupPrivilegeValue(MachineName,SE_BACKUP_NAME,&luid))

{

PERR("LookupPrivilegeValue",GetLastError());

return RTN_ERROR;

}

tp.PrivilegeCount=1;

tp.Privileges[0].Luid=luid;

tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),

NULL,NULL);

if(GetLastError()!=ERROR_SUCCESS)

{

PERR("AdjustTokenPrivileges",GetLastError());

return RTN_ERROR;

}

//仅当机器名非空时,连接远程机器,否则默认为本地机器

if(MachineName!=NULL)

{

if((rc=RegConnectRegistry(MachineName,

HKEY_LOCAL_MACHINE,

&hKey))!=ERROR_SUCCESS)

//上面这个远程连接的函数过会再解释

{

PERR("RegConnectRegistry",rc);

return RTN_ERROR;

}

}

else hKey=HKEY_LOCAL_MACHINE;

while((rc=RegEnumKeyEx(

hKey,

dwSubKeyIndex,

szSubKey,

&dwSubKeyLength,

NULL,

NULL,

NULL,

NULL)

)!=ERROR_NO_MORE_ITEMS){//枚举子键if(rc==ERROR_SUCCESS)

{

LONG lRetVal;//SaveRegistrySubKey子函数的返回值

#ifdef DEBUG

fprintf(stdout,"Saving%s\n",szSubKey);

#endif

//保存szSubKey变量里的子键名到文件,文件名由变量szSubKey决定

if((lRetVal=SaveRegistrySubKey(hKey,szSubKey,szSubKey) )!=ERROR_SUCCESS)

{

PERR("SaveRegistrySubKey",lRetVal);

}

//子键索引加1,指向下一个子键

dwSubKeyIndex++;

//重置buffer大小

dwSubKeyLength=_MAX_FNAME;

//继续保存

continue;

}

else

{

//如果枚举完毕,没有其他的子键

//调用子函数PERR,给出出错信息,保存工作结束

PERR("RegEnumKeyEx",rc);

return RTN_ERROR;

}

}//至此枚举保存部分结束

//回收句柄,释放内存

RegCloseKey(hKey);

//唤醒这个线程掌控的所有权限(包括备份)

AdjustTokenPrivileges(hToken,TRUE,NULL,0,NULL,NULL);

//回收线程

CloseHandle(hToken);

return RTN_OK;

}

LONG SaveRegistrySubKey(//负责保存的子函数

HKEY hKey,//指向要保存“母键”的句柄

LPTSTR szSubKey,//指针,存放保存子键键名

LPTSTR szSaveFileName//指针,指向保存路径/文件名

)

{

HKEY hKeyToSave;//指向要保存“子键”的句柄

LONG rc;//RegXxx一类函数的返回值

DWORD dwDisposition;

if((rc=RegCreateKeyEx(hKey,

szSubKey,//要打开的子键

0,

NULL,

REG_OPTION_BACKUP_RESTORE,//由winnt.h文件声明

KEY_QUERY_VALUE,//最小的权限,安全起见

NULL,

&hKeyToSave,

&dwDisposition)

)==ERROR_SUCCESS)

{

//保存子键,如果机器名是一台远程机器的,保存的文件将被存放在远程机器上

rc=RegSaveKey(hKeyToSave,szSaveFileName,NULL);

//回收句柄

RegCloseKey(hKeyToSave);

}

//返回子函数的值

return rc;

}

void PERR(//API函数失败“专用报错函数”:)

LPTSTR szAPI,//指向失败的API函数名的指针

DWORD dwLastError//和API相关的最近一次失败值

)

{

LPTSTR MessageBuffer;

DWORD dwBufferLength;

//

//把失败信息输出到文件

//

fprintf(stderr,"%s error!(rc=%lu)\n",szAPI,dwLastError);

if(dwBufferLength=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|

FORMAT_MESSAGE_FROM_SYSTEM,

NULL,

dwLastError,

LANG_NEUTRAL,

(LPTSTR)&MessageBuffer,

0,

NULL))

{

DWORD dwBytesWritten;

//

//给我的感觉像是在记录日志

//

WriteFile(GetStdHandle(STD_ERROR_HANDLE),

MessageBuffer,

dwBufferLength,

&dwBytesWritten,

NULL);

//

//释放buffer

//

LocalFree(MessageBuffer);

}

}

虽然这个程序长了点,但是作者注释得很好,看起来也不费力气。除了翻译作者的部分注释,我也添加了一些个人注释,希望各位看的不是很累。

回过头,我们来简单提一下RegConnectRegistry这个远程连接函数。

函数本身很简单:

LONG RegConnectRegistry(

LPTSTR lpMachineName,

//指针,指向远程计算机名

HKEY hKey,//句柄

PHKEY phkResult//指针,指向远程句柄的buffer

);

LPTSTR lpMachineName:存放的格式是\\计算机名,如果为空,默认为本地机器

HKEY hKey:可以是下面几个句柄中的一个(都是在远程机器上的)

HKEY_LOCAL_MACHINE

HKEY_USERS

Windows NT:HKEY_PERFORMANCE_DATA,如果远程机器跑得是Windows NT

Windows95and Windows98:HKEY_DYN_DATA,如果远程机器跑得是Windows95或

Windows98

Windows95and Windows98:HKEY_CURRENT_CONFIG,如果远程机器跑得是Windows

95或Windows98

需要指出的是,HKEY_CLASSES_ROOT和HKEY_CURRENT_USER不能用在这里

PHKEY phkResult:用于接收远程机器返回句柄的指针

如果使用正确,函数返回值为ERROR_SUCCESS。需要记住一点,使用完这个函数,要记得用RegCloseKey回收句柄。我在网上看过一些朋友发的源码,功能实现的都不错,就是会忘记回收句柄。其实,有时候不回收,不会对软件功能造成太大的影响,但对资源是一种浪费。同时,不回收句柄相当于在远程机器上留下了你作案的痕迹,如果你在干坏事的话,呵呵。远程备份注册表,我还没有尝试,可能会设计到一些权限的问题。但是就我所知,网上流行的一些看远程机器运行什么服务的软件,大多使用了这个功能,远程读取注册表。以后有时间,我会把这部分整理出来。

说到现在,你是不是也会和我一样有一个疑问。如果使用今天讲的内容备份任意一个子键,结果和上一次说的方法(使用RegSaveKey)是否一样?答案是肯定的,在附录里,我列出了我修改的一个程序,针对

“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentSetControl\\Services\\Tcpip\\Parame ters”这个键使用了今天说的这个方法,得到的结果和上次的一模一样,都把所有的子键名和值保存了下来,文件一个字节都不差。这也就是为什么我郁闷的原因,呵呵。如果对于根键,也可以直接使用RegSaveKey保存,那么MSDN这篇文章就显得罗嗦了,凭空多用了一个枚举函数。不过大家放心,目前我只使用RegSaveKey测试的结果还没有成功过。下一次我会告诉大家究竟行不行。

不过需要记住的一点是,RegSaveKey只能保存Nonvolatile的键。所谓的Nonvolatile指得是信息存放在文件里,机器重启以后依然保存的性质。也许我上面说得两种方法得到一样的结果是因为我都是对Nonvolatile的键进行操作。这部分工作也可以进一步深挖一下。

好了,这次就说到这里。下一次我们要开始讲述如何修改注册表的值。

Thank you so much for reading this article.

============================================================================ APPENDIX:

针对一个子键使用先枚举后保存的例子,得到的结果和第二部分的一样。(本文所有程序在VC6.0, windowsXP Pro环境下调试成功)其实我仅仅换了一条语句而已。方便大家测试,就全贴出来了。

/*Save HKEY_LOCAL_MACHINE registry key,each subkey saved to a file of

*name subkey

*

*this allows us to get around security restrictions which prevent

*the use of RegSaveKey()on the root key

*

*the optional target machine name is specified in argv[1]

*

*v1.21

*Scott Field(sfield)01-Apr-1995

*/

#include

#include

#include

#include

#define RTN_OK0

#define RTN_USAGE1

#define RTN_ERROR13

LONG SaveRegistrySubKey(HKEY hKey,LPTSTR szSubKey,LPTSTR szSaveFileName); void PERR(LPTSTR szAPI,DWORD dwLastError);

int main(int argc,char*argv[])

{

TOKEN_PRIVILEGES tp;

HANDLE hToken;

LUID luid;

LONG rc;

HKEY hKey,hResult;

LPTSTR MachineName=NULL;

DWORD dwSubKeyIndex=0;

char szSubKey[_MAX_FNAME];

DWORD dwSubKeyLength=_MAX_FNAME;

CString strKey="SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";

if(argc==2)MachineName=argv[1];

if(!OpenProcessToken(GetCurrentProcess(),

TOKEN_ADJUST_PRIVILEGES,

&hToken))

{

PERR("OpenProcessToken",GetLastError());

return RTN_ERROR;

}

if(!LookupPrivilegeValue(MachineName,SE_BACKUP_NAME,&luid))

{

PERR("LookupPrivilegeValue",GetLastError());

return RTN_ERROR;

}

tp.PrivilegeCount=1;

tp.Privileges[0].Luid=luid;

tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),

NULL,NULL);

if(GetLastError()!=ERROR_SUCCESS)

{

PERR("AdjustTokenPrivileges",GetLastError());

return RTN_ERROR;

}

if(MachineName!=NULL)

{

if((rc=RegConnectRegistry(MachineName,

HKEY_LOCAL_MACHINE,

&hKey))!=ERROR_SUCCESS)

{

PERR("RegConnectRegistry",rc);

return RTN_ERROR;

}

}

else hKey=HKEY_LOCAL_MACHINE;

RegOpenKeyEx(hKey,(LPCTSTR)strKey,0,KEY_ALL_ACCESS,&hResult);//注意:改动的地方在这里

while((rc=RegEnumKeyEx(

hResult,

dwSubKeyIndex,

szSubKey,

&dwSubKeyLength,

NULL,

NULL,

NULL,

NULL)

)!=ERROR_NO_MORE_ITEMS){

if(rc==ERROR_SUCCESS)

{

LONG lRetVal;

#ifdef DEBUG

fprintf(stdout,"Saving%s\n",szSubKey);

#endif

if((lRetVal=SaveRegistrySubKey(hResult,szSubKey,szSubKey)

)!=ERROR_SUCCESS)

{

PERR("SaveRegistrySubKey",lRetVal);

}

dwSubKeyIndex++;

dwSubKeyLength=_MAX_FNAME;

continue;

}

else

{

PERR("RegEnumKeyEx",rc);

return RTN_ERROR;

}

}

RegCloseKey(hResult);

AdjustTokenPrivileges(hToken,TRUE,NULL,0,NULL,NULL); CloseHandle(hToken);

return RTN_OK;

}

LONG SaveRegistrySubKey(

HKEY hKey,

LPTSTR szSubKey,

LPTSTR szSaveFileName

)

{

HKEY hKeyToSave;

LONG rc;

DWORD dwDisposition;

if((rc=RegCreateKeyEx(hKey,

szSubKey,

0,

NULL,

REG_OPTION_BACKUP_RESTORE,

KEY_QUERY_VALUE,

NULL,

&hKeyToSave,

&dwDisposition)

)==ERROR_SUCCESS)

{

rc=RegSaveKey(hKeyToSave,szSaveFileName,NULL);

RegCloseKey(hKeyToSave);

}

return rc;

相关文档