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 平台开发注意要点之注册表重定向的更多相关文章

  1. 64 位 Windows 平台开发注意要点之文件系统重定向

    Program Files 的重定向 很多开发人员都知道,在 64 位 Windows 系统上,32 位程序是无法获取得到 C:\Program Files 的完整路径的,只能获取到 C:\Progr ...

  2. 64位Windows操作系统中的注冊表

    x64系统上有x64.x86两种注冊表,记录下. 64 位Windows系统中的注冊表分为 32 位注冊表项和 64 位注冊表项.很多 32 位注冊表项与其对应的 64 位注冊表项同名. 在64位版本 ...

  3. 64位WINDOWS系统环境下应用软件开发的兼容性问题(CPU 注册表 目录)

    应用软件开发的64 位WINDOWS 系统环境兼容性 1. 64 位CPU 硬件 目前的64位CPU分为两类:x64和IA64.x64的全称是x86-64,从名字上也可以看出来它和 x86是兼容的,原 ...

  4. 64位 Windows 用了 32位编译平台 编译不过 MySQL API

    发生在一周前的事情了,当时想感受下 MySQL C API ,就写了几个小例子.虽然是在 Windows(我的工作电脑是 64位 Windows) 上面,但是不想用 VS ,只想用文本软件写好代码后用 ...

  5. [百度空间] [转]将程序移植到64位Windows

    from : http://goooder.bokee.com/2000373.html (雷立辉 整理) 简介:本文对如何将32位Windows程序平滑的支持和过渡到64位Windows操作系统做出 ...

  6. 【转载】64 位 Windows 内核虚拟地址空间布局(基于 X64 CPU)

    原文链接:http://shayi1983.blog.51cto.com/4681835/1734822 本文为原创翻译,原文出处为 http://www.codemachine.com/articl ...

  7. Winio驱动在64位windows下无法使用的解决方法

    C#在使用WinIo的驱动开发类似按键精灵一类工具的时候,需要对相关的驱动进行注册才能正常启动,找了下资料,资料来自: http://jingyan.baidu.com/article/642c9d3 ...

  8. 解决Tomcat6解压版在64位windows系统上无法启动服务的问题

    解决Tomcat6解压版在64位windows系统上无法启动服务的问题         由于客户环境为64位windows系统,开发环境一直用32位.tomcat使用6.0.20非安装版.部署时发现在 ...

  9. 在64位windows下使用instsrv.exe和srvany.exe创建windows服务

    在64位windows下使用instsrv.exe和srvany.exe创建windows服务   在32位的windows下,包括windows7,windows xp以及windows 2003, ...

随机推荐

  1. Linux系统安装与初用

    1.结合实验尝试,并查阅资料,总结在实验准备中提出的7个问题. (1).Linux的发行版本.内核版本:二者区别与联系. 核心版本主要是Linux的内核,发行版本是各个公司推出的版本,他们与核心版本是 ...

  2. react react-native 日期插件 m-date-picker / rmc-date-picker的使用

    m-date-picker 基于 React,提供了 iOS 风格的日期选择方式,与原生 Datepicker 非常相似. 主页: https://github.com/react-component ...

  3. ansible 自动化运维

    Ansible 自动化运维 ansible安装epel #yum list all *ansible*#yum install *ansible*#yum info ansible#rpm -ql a ...

  4. JavaScript中易混淆的DOM属性及方法对比

    JavaScript中易混淆的DOM属性及方法对比 ParentNode.children VS Node.prototype.childNodes ParentNode.children:该属性继承 ...

  5. Visual C++ 6.0中if..else..的简单用法和基本格式

    # include <stdio.h> int main (void) { float score; printf("请输入您的考试成绩:"); scanf(" ...

  6. Apache HTTP 服务器 2.4(又名httpd)安装\配置 \启动

    Apache HTTP 服务器 2.4 源文档  

  7. linux配置服务器

    梳理一下这次配置服务器的思路. 1,挂载磁盘 Java和neigx上传到根目录下,tomcat放在data目录下,数据库新建文件夹也在data下, 2,配置环境变量 3,nginx修改域名 4,数据库 ...

  8. ROS * 通过launch文件添加多个模型

    我添加的是dae模型,urdf文件过两天贴 方法一 : <launch> <!-- these are the arguments you can pass this launch ...

  9. unity 面试题(答案)

    一.什么是渲染管道?是指在显示器上为了显示出图像而经过的一系列必要操作.渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去.主要步骤有:本地坐标->视图坐标->背面裁 ...

  10. 去除 chrome 上保存密码后的 input 框的屎黄色背景

    网上找的设置 background-color,background-image 没用,后来找到这个方法测试有效: input:-webkit-autofill { transition: backg ...