所在的位置: C++ >> C++优势 >> 使用新式C访问Windows

使用新式C访问Windows

治好一位白癜风幸福一个家 http://baidianfeng.39.net/a_ht/140521/4392563.html

WindowsOS公开了一系列C接口API,以方便开发者访问注册表。其中一些API的级别相当低,需要程序员注意许多细节。自WindowsVista起,这一大家庭又新增了一种更高级别的API:RgGtValu函数(bit.ly/jXtfpJ)。在引入此API之前,若要读取注册表中的值,必须先调用RgOpnKyEx来打开包含值的相应注册表项。然后,必须调用RgQuryValuExAPI,同时处理许多复杂的细节。例如,如果使用RgQuryValuEx读取字符串值,则不能保证返回的字符串以NUL正确结尾,这可能会导致代码中出现一系列危险的安全bug。为了防止这种情况发生,务必要检查返回的字符串是否以NUL结尾;如果不是,需要手动添加。此外,还务必要使用RgClosKy正确关闭已打开的注册表项。

当然,打开注册表项可能会失败。因此,为了处理此问题,还必须添加代码。RgGtValuAPI简化了这一工作流,因为它会自动打开并在使用后关闭所需的注册表项,同时还会先在字符串末尾正确添加NUL,然后再将字符串返回给调用方。虽然进行了这样的简化,但RgGtValu函数仍是低级别C接口函数;此外,该函数实际上可以处理多种不同类型的注册表值(包括DWORD、字符串和二进制数据),因此接口编程起来十分复杂。

万幸的是,可以使用新式C++在此RgGtValuWin3API基础之上正确生成更高级别的抽象,提供一个用于读取注册表中不同类型值的简便接口。

使用异常表示错误

RgGtValuAPI是C接口API,因此会使用返回代码向调用方指明错误条件。特别是,此函数返回LONG类型的值:如果成功,返回ERROR_SUCCESS(即值为零);如果出错,返回其他值。例如,如果调用方提供的输出缓冲区不够大,导致API无法写入数据,此函数返回ERROR_MORE_DATA。为了在此CAPI基础之上生成更高级别的C++接口,可以定义用于表示错误的C++异常类。此类可以派生自标准的std::runtim_rror类,能够在其中嵌入RgGtValu返回的LONG错误代码:

classRgistryError:publicstd::runtim_rror{public:...privat:LONGm_rrorCod;};

此外,还可以在异常对象中嵌入其他信息;例如,HKEY和子项的名称。这就是基本实现代码。

可以添加构造函数,使用失败的RgGtValu调用生成的错误消息和返回代码创建此异常类的实例:

RgistryError(constchar*mssag,LONGrrorCod):std::runtim_rror{mssag},m_rrorCod{rrorCod}{}

可以向使用只读取值函数(gttr)的客户端公开错误代码:

LONGErrorCod()constnoxcpt{rturnm_rrorCod;}

至此,你已生成此异常类,可以继续将RgGtValuCAPI包装在更高级别的C++接口中,此接口不仅更易于使用,还能减少出错。

读取注册表中的DWORD值

让我们从下面的简单操作入手:使用RgGtValuAPI读取注册表中的DWORD值。此示例中的使用模式相当简单。但首先,让我们看看可以在C++中定义什么类型的接口来管理这种情况。

下面是RgGtValuAPI原型:

LONGWINAPIRgGtValu(_In_HKEYhky,_In_opt_LPCTSTRlpSubKy,_In_opt_LPCTSTRlpValu,_In_opt_DWORDdwFlags,_Out_opt_LPDWORDpdwTyp,_Out_opt_PVOIDpvData,_Inout_opt_LPDWORDpcbData);

如你所见,此C接口函数需要使用高度泛型数据,如void*输出缓冲区(pvData)和输入/输出缓冲区大小参数(pcbData)。此外,还有标识注册表项及其下特定值名称的C样式字符串(lpSubKy和lpValu)。可以略微调整一下此C函数原型,使其更易于C++调用方使用。

首先,由于要指明抛出C++异常的错误条件,C++包装器可以直接以返回值的形式返回从注册表中读取的DWORD值。这样一来,便自动无需使用原始void*输出缓冲区参数(pvData)和相关大小参数(pcbData)。

此外,由于要使用C++,最好使用std::wstring类(而不是C样式的原始指针)表示Unicod(UTF-16)字符串。所以,可以定义更为简单的C++函数,从而读取注册表中的DWORD值:

DWORDRgGtDword(HKEYhKy,conststd::wstringsubKy,conststd::wstringvalu)

如你所见,其中没有PVOID和LPDWORD参数;输入字符串通过常量引用传递到std::wstring对象,从注册表中读取的值则以DWORD的形式由此C++函数返回。这绝对是一个更简单的更高级别接口。

现在,让我们深入探究实现代码。如前所述,在此示例中,RgGtValu的调用模式相当简单。只需声明一个DWORD变量,用于存储从注册表中读取的值:

DWORDdata{};

然后,需要声明另一个DWORD变量,用于表示RgGtValu写入的输出缓冲区的大小(以字节为单位)。请注意,这个简单示例中的输出缓冲区就是以前的“数据”变量,它的大小始终是DWORD的大小:

DWORDdataSiz=sizof(data);

不过,请注意,不能将dataSiz标记为常量,因为它是RgGtValu的输入和输出参数。

然后,可以调用RgGtValuAPI:

LONGrtCod=::RgGtValu(hKy,subKy.c_str(),valu.c_str(),RRF_RT_REG_DWORD,nullptr,data,dataSiz);

输入wstring对象通过wstring::c_str方法转换成原始C样式字符串指针。RRF_RT_REG_DWORD标志将注册表值的类型限制为DWORD。如果要读取的注册表值为不同类型,那么出于稳妥考虑RgGtValu函数调用会失败。

最后两个参数表示输出缓冲区的地址(在此示例中,为数据变量的地址)和用于存储输出缓冲区大小的变量的地址。实际上,在返回时,RgGtValu会报告写入输出缓冲区的数据的大小。在读取简单DWORD值的示例中,数据大小始终为4字节,即sizof(DWORD)。然而,此大小参数对大小可变的值(如字符串)更为重要;我将在本文后面对此进行介绍。

调用RgGtValu函数后,可以检查返回代码,并在出错时抛出异常:

if(rtCod!=ERROR_SUCCESS){throwRgistryError{"CannotradDWORDfromrgistry.",rtCod};}

请注意,RgGtValu返回的错误代码(rtCod)已嵌入异常对象中,稍后可由处理异常的代码进行检索。

相反,如果成功,DWORD数据变量可以直接返回给调用方:

rturndata;

以上便是对函数实现代码的介绍。

调用方可以使用下面的代码直接调用此C++包装器函数:

DWORDdata=RgGtDword(HKEY_CURRENT_USER,subky,L"MyDwordValu");

请注意,与原始RgGtValuCAPI调用相比,此代码非常简单。只需将句柄传递给打开的注册表项(在此示例中,为HKEY_CURRENT_USER预定义项),即包含子项和值名称的字符串。如果成功,DWORD值会返回给调用方。然而,如果出错,则会抛出RgistryError类型的自定义异常。与调用RgGtValu相比,这种代码不仅级别更高,而且要简单得多。实际上,RgGtValu的复杂性是被隐藏在这个自定义RgGtDwordC++包装器函数中了。

可以通过同样的方式从注册表中读取QWORD(64位数据)值;在这种情况下,只需将注册表值的DWORD类型替换为64位ULONGLONG。

读取注册表中的字符串值

读取注册表中的DWORD值相当简单:只需调用一次RgGtValuWin3API就已足够。这主要是因为DWORD值的大小不变,DWORD的大小始终为4字节。相比之下,读取注册表中的字符串又加了一层复杂性,因为字符串是大小可变的数据。在这种情况下,我的想法是调用RgGtValuAPI两次:在第一次调用中,请求此API返回输出字符串缓冲区的相应大小。接下来,动态分配适当大小的缓冲区。最后,第二次调用RgGtValu,将字符串数据写入之前分配的缓冲区中。(我在之前的文章“在Win3API边界使用STL字符串”(msdn.


转载请注明:http://www.aierlanlan.com/grrz/91.html