64 位 Windows 平台开发注意要点之注册表重定向
Window 系统错误代码 ERROR_SUCCESS,本博客中一律使用 NO_ERROR 代替。虽然 ERROR_SUCCESS 与 NO_ERROR 是完全等价的,都代表成功,但是后者却和其他错误代码一样,使用 ERROR 前缀,容易让人误认为是错误代码。而 NO_ERROR 意义很明显,就是无错误。还有另外一个宏 NOERROR 也表示成功,但是使用较少。Windows 系统错误代码的数据类型,其类型微软并没有具体说明。来自 advapi32.dll 中的注册表操作函数多使用 LONG 作为返回值,而来自 shlwapi.dll 中的注册表操作包装函数使用 LSTATUS 作为返回值。为保持统一,本博客统一使用 DWORD 作为 Windows 错误代码数据类型,这是因为 GetLastError 的返回值类型是 DWORD。
作为 Windows 开发人员,注册表是必须要了解的,读写注册表也是很平常的事情。然而,现实中也发现好多程序员对注册表的有些细节并不了解,尤其是在 64 位系统上重定向,以及 NT 6.0 开始推出的注册表虚拟化。
MSDN 上的说法是:注册表虚拟化是一种应用程序兼容技术,让那些可能带来全局影响的注册表写入操作重定向到每个用户的位置。这个读取或者写入重定向对于程序而言都是透明的。该技术从 Windows Vista 开始支持。(原文:Registry virtualization is an application compatibility technology that enables registry write operations that have global impact to be redirected to per-user locations. This redirection is transparent to applications reading from or writing to the registry. It is supported starting with Windows Vista.)
看的出来微软推出这个技术的目的。准确的来说就是,因为向 HKEY_LOCAL_MACHINE(以下简称 HKLM)写入注册表,是会影响到电脑上的所有用户,为了避免这种全局的影响,微软针对其写入操作进行了重定向。究其根本原因,就是 Windows XP 上并没有 UAC,任何程序都可以随意写入 HKLM。然而,从 Windows Vista 开始引入 UAC 之后,微软当然不允许低权限程序来随意操作 HKLM 了,但这样的话又可能会权限问题写入失败,就有可能导致程序运行出错,所以,为了早期的程序能正常运行且不影响现有注册表,微软引入了这个技术,以保证老的程序不会因为权限问题导致注册表写入失败。那么如何避免重定向呢?微软说要嵌入 manifest 并指定应用程序的执行权限,否则程序的注册表读写操作将注册表虚拟化技术重定向到其他位置。manifest 文件在 Visual Studio 中被称为清单文件。关于清单文件,将在其他文章进行讨论,在此我们这里只讨论执行权限级别设置,即 requestedExecutionLevel 这个节点。
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
其中 level 属性值 asInvoker,还可以是 highestAvailable 或 requireAdministrator。意义如下:
- asInvoker
以和调用该程序的进程同样的权限级别执行。也可以在右键菜单中选择使用管理员权限执行,但程序不会主动请求管理员权限,即便当前用户具备以管理员执行的条件。 - highestAvailable
以当前用户可以获得的最高权限来执行。即当前用户具备以管理员执行的条件时,会请求管理员权限,这种情况下和 requireAdministrator 一样。如果当前用户不具备管理员权限,则类似于 asInvoker 的情况。 - requireAdministrator
始终请求管理员权限。如果当前用户不具备管理员权限,则程序无法执行。
如果程序并没有嵌入清单文件,或者嵌入的清单文件并没有指定执行权限,那么程序的注册表写入将会被重定向,而不是返回失败。如下面的代码:
BOOL WINAPI RegWriteStringTest(void)
{
HKEY hKey = NULL;
DWORD dwError = RegCreateKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\KeyName"), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
if (dwError == NO_ERROR)
{
TCHAR szValue[] = _T("ValueName");
TCHAR szData[] = _T("ValueData");
DWORD dwSize = lstrlen(szData) * sizeof(TCHAR) + sizeof(TCHAR);
dwError = RegSetValueEx(hKey, szValue, 0, REG_SZ, (BYTE *)szData, dwSize);
RegCloseKey(hKey);
if (dwError == NO_ERROR)
{
return TRUE;
}
}
return FALSE;
}
在程序未嵌入清单文件或者其中不包含权限信息时,且程序未以管理员权限执行的情况下,期望的返回值是 ERROR_ACCESS_DENIED,实际的返回值却是 NO_ERROR,调用 RegSetValueEx 也同样会成功。然而,打开注册表编辑器在 HKLM\SOFTWARE\KeyName 下查看,却发现并没有写入任何信息。使用 RegSnap 建立执行前后两个注册表快照,对比之后发现,注册表的写入被重定向到:
HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\SOFTWARE\KeyName
当读取的时候,也是从上述位置读取,因此实际上也返回成功。就会造成一种假象:注册表系列 API 有 BUG,明明没有写入任何值,结果却返回成功,而且看似根本没写进去的值还能再次读取成功。
关于注册表虚拟化的更多信息,请访问:
https://msdn.microsoft.com/en-us/library/aa965884.aspx
在 64 位系统上,32 位程序读写部分注册表路径时会被系统重定向,这有些类似于读写 System32 文件夹的处理方式。比如,写入 HKLM\Software\KeyName,却发现实际写入到 HKLM\Software\Wow6432Node\KeyName,读取亦是如此。现实中发现,很多的程序员在检测一个程序在 HKLM 键下面的注册表信息,通常会针对 HKLM\Software 和 HKLM\Software\Wow6432Node 分别检查,实际上这样检查毫无效果。对于 32 位程序而言,访问 HKLM\Software 时,系统底层会重定向到 HKLM\Software\Wow6432Node,并不能得到真正的 HKLM\Software 下面的信息,即便再访问一次 HKLM\Software\Wow6432Node,经测试也是访问 Wow6432Node 下面的值,和直接访问 HKLM\Software 并没有任何区别。如果你仔细阅读 MSDN 上关于注册表重定向和访问权限等资料,会发现微软提供了两个特殊的注册表权限位:KEY_WOW64_32KEY、KEY_WOW64_64KEY,来控制访问权限。所以,当使用 RegOpenKeyEx 或 RegCreateKeyEx 访问注册表的 HKCR 或 HKLM\Software 下的路径,不需要显式指定 Wow6432Node,而是应当通过其权限位,如 KEY_READ,和上述二者之一进行组合来控制具体的访问位置。如果开发者显示指定 HKLM\Software\Wow6432Node,则程序在任何情况下都是访问这个路径。但是在 32 位系统中,这个路径默认并不存在,如果强行创建,依然没有任何意义。为了保持统一以及遵循 API 的规范,我们应该做到不显式指定 Wow6432Node 子键。如果不通过权限位进行访问视图控制,可能会造成代码逻辑混乱,如访问不同的注册表路径实际上底层逻辑相同,或者同样的代码编译为 32 位或 64 位后逻辑不一致等等。所以,如果要检测 32 位和 64 位注册表 HKLM\SOFTWARE\KeyName 下是否存在 ValueName,规范的代码如下:
BOOL WINAPI RegCheckValueTest(void)
{
DWORD dwWowFlags[] = { KEY_WOW64_32KEY, KEY_WOW64_64KEY };
DWORD dwWowCount = ARRAYSIZE(dwWowFlags);
for (size_t i = 0; i < dwWowCount; i++)
{
HKEY hKey = NULL;
DWORD dwAccess = KEY_READ | dwWowFlags[i];
DWORD dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\KeyName"), dwAccess, &hKey);
if (dwError == NO_ERROR)
{
dwError = RegQueryValueEx(hkeySub, _T("ValueName"), NULL, NULL, NULL, NULL);
RegCloseKey(hKey);
if (dwError == NO_ERROR)
{
return TRUE;
}
}
}
return FALSE;
}
在不同 CPU 位数的系统上,32 位和 64 位程序分别使用不同的权限位组合访问 HKLM\Software 时,系统底层实际访问的注册表位置如下表所示。
| 系统架构 | 程序架构 | 显式访问路径 | 实际访问路径 | 备注 | ||
| 权限位不含 KEY_WOW64_* | 权限位包含 KEY_WOW64_32KEY | 权限位包含 KEY_WOW64_64KEY | ||||
| 32 位系统 | 32 位程序 | HKLM\Software | HKLM\Software | 原因:在 32 位系统上不存在不同访问视图 影响:参数 KEY_WOW64_* 被系统忽略 |
||
| 64 位系统 | 32 位程序 | HKLM\Software | HKLM\Software\Wow6432Node | HKLM\Software\Wow6432Node | HKLM\Software | |
| 64 位程序 | HKLM\Software | HKLM\Software | ||||
| 32 位程序 | HKLM\Software\Wow6432Node | HKLM\Software\Wow6432Node | 原因:在路径中显式指定了 Wow6432Node 节点 影响:参数 KEY_WOW64_* 被系统忽略 |
|||
| 64 位程序 | ||||||
可见,32 位程序访问注册表 HKLM\Software 路径时,默认会被重定向到 HKLM\Software\Wow6432Node,如果权限位指定 KEY_WOW64_64KEY 时则访问 HKLM\Software。64 位程序访问注册表 HKLM\Software 路径时,默认会访问 HKLM\Software,如果权限位指定 KEY_WOW64_32KEY 时则访问 HKLM\Software\Wow6432Node。当然,前提是程序并没有受到注册表虚拟化影响,否则会被写入到以下注册表位置:
HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\SOFTWARE
HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\SOFTWARE\Wow6432Node
实际观察发现 HKCU\SOFTWARE\Wow6432Node 下面只有极少量的数据,因此 HKCU\SOFTWARE\Wow6432Node 下面(包括其他从此处映射的键)的注册表键通常可以忽略。这可以说明,注册表针对 32 和 64 位的重定向仅针对 HKLM(包括其他从此处映射的键)有效,如果要访问 HKCU 下面的节点,通常无需考虑重定向的问题。而在 32 位系统上,不存在注册表重定向的问题。
关于注册表重定向的更多信息,请访问:
https://msdn.microsoft.com/en-us/library/aa384253.aspx
https://msdn.microsoft.com/en-us/library/aa384129.aspx
64 位 Windows 平台开发注意要点之注册表重定向的更多相关文章
- 64 位 Windows 平台开发注意要点之文件系统重定向
Program Files 的重定向 很多开发人员都知道,在 64 位 Windows 系统上,32 位程序是无法获取得到 C:\Program Files 的完整路径的,只能获取到 C:\Progr ...
- 64位Windows操作系统中的注冊表
x64系统上有x64.x86两种注冊表,记录下. 64 位Windows系统中的注冊表分为 32 位注冊表项和 64 位注冊表项.很多 32 位注冊表项与其对应的 64 位注冊表项同名. 在64位版本 ...
- 64位WINDOWS系统环境下应用软件开发的兼容性问题(CPU 注册表 目录)
应用软件开发的64 位WINDOWS 系统环境兼容性 1. 64 位CPU 硬件 目前的64位CPU分为两类:x64和IA64.x64的全称是x86-64,从名字上也可以看出来它和 x86是兼容的,原 ...
- 64位 Windows 用了 32位编译平台 编译不过 MySQL API
发生在一周前的事情了,当时想感受下 MySQL C API ,就写了几个小例子.虽然是在 Windows(我的工作电脑是 64位 Windows) 上面,但是不想用 VS ,只想用文本软件写好代码后用 ...
- [百度空间] [转]将程序移植到64位Windows
from : http://goooder.bokee.com/2000373.html (雷立辉 整理) 简介:本文对如何将32位Windows程序平滑的支持和过渡到64位Windows操作系统做出 ...
- 【转载】64 位 Windows 内核虚拟地址空间布局(基于 X64 CPU)
原文链接:http://shayi1983.blog.51cto.com/4681835/1734822 本文为原创翻译,原文出处为 http://www.codemachine.com/articl ...
- Winio驱动在64位windows下无法使用的解决方法
C#在使用WinIo的驱动开发类似按键精灵一类工具的时候,需要对相关的驱动进行注册才能正常启动,找了下资料,资料来自: http://jingyan.baidu.com/article/642c9d3 ...
- 解决Tomcat6解压版在64位windows系统上无法启动服务的问题
解决Tomcat6解压版在64位windows系统上无法启动服务的问题 由于客户环境为64位windows系统,开发环境一直用32位.tomcat使用6.0.20非安装版.部署时发现在 ...
- 在64位windows下使用instsrv.exe和srvany.exe创建windows服务
在64位windows下使用instsrv.exe和srvany.exe创建windows服务 在32位的windows下,包括windows7,windows xp以及windows 2003, ...
随机推荐
- 非对称加密, 助记词, PIN, WIF
一钱包 1.1非对称加密, 助记词, PIN, WIF, 地址 1.1.1 非对称加密算法 非对称加密算法, 加密与解密使用不同的KEY, 我们分别称为私钥与公钥,其中可以通过私钥生成公钥 在比特币中 ...
- 【等价转换】—— min/max 的转换与互相转换
0. min 与 max 的转换 {max(X,Y)=X+Y−min(X,Y)min(X,Y)=X+Y−max(X,Y)min(X,Y)+max(X,Y)=X+Y" role="p ...
- 如何让浏览器直接输出HTML代码而不解析
方法一: 将HTML代码嵌入到<script type='text/html' style='display:block'></scipt>中 <script type= ...
- 尝鲜svnup
最近有同事折腾了一下svnup的编译,终于可以在Mac OS X和Linux上面编译通过了,仓库在这里:https://github.com/lvzixun/svnup/ svnup这个工具只有一个功 ...
- 采用NoteExpress参考文献导入和导出
1.NoteExpress使用 该软件分为企业版和个人版,但目前看来个人版已经不能用了,我用的是我们学校购买的软件,感觉还是很方便的. (1)首先建立数据库: (2)可以通过导入原文选项将下载好的文章 ...
- js: 文件(excel)下载处理
以前很少接触文件下载的功能,昨天和后台开发人员联调下载功能出现了问题,一开始我请求接口,返回二进制文件流乱码,在网上找了方法,可以解决.后面后台开发人员改了一下,返回文件地址,然后就解决了.所以我了解 ...
- Shell脚本中的逻辑判断、文件目录属性判断、if的特殊用法、case判断
1.Shell脚本中的逻辑判断 格式1:if 条件 ; then 语句; fi格式2:if 条件; then 语句; else 语句; fi格式3:if …; then … ;elif …; then ...
- 我对CSS的认识
花费了一段时间,终于对HTML有了一定的了解,随后又开始进行CSS的战斗感觉自己学起来有一点吃力.我就简单的讲一下我所学到的吧! CSS的概述. 层叠式样式表,用于控制网页样式并允许将样式信息并允许将 ...
- ubuntu 上安装node.js 的简单方法
一.安装 1.$ sudo apt-get install nodejs 2.$ sudo apt-get install npm 二.升级 1.升级npm命令如下: $ sudo npm i ...
- Django学习笔记之上下文处理器和中间件
上下文处理器 上下文处理器是可以返回一些数据,在全局模板中都可以使用.比如登录后的用户信息,在很多页面中都需要使用,那么我们可以放在上下文处理器中,就没有必要在每个视图函数中都返回这个对象. 在set ...