「安装程序写入的许可证密钥,应用启动时读不到。用 regedit 看确实有这个值」──在协助某设备联动软件进行 64位迁移时,我们收到过这样的报告。查下去才发现,值实际存在于 HKLM\Software\Wow6432Node\公司名 之下。而新编译成 64位的应用,读取的却是 HKLM\Software\公司名,那里什么都没有——这就是整件事的真相。
这类「写入的值找不到了」「regedit 里看得到,应用却看不到」的问题,如果不了解两个机制,无论怎么用肉眼检查都只会一头雾水。第一个机制是,64位 Windows 上的注册表会根据进程的位数(该进程是以 32位方式运行,还是以 64位方式运行的区别)而出现在不同的位置──也就是由 WOW64(Windows 32-bit on Windows 64-bit,64位 Windows 用来运行 32位应用的子系统)带来的注册表重定向。另一个机制是,权限不足的写入会被悄悄转发到别的位置──也就是 UAC 的注册表虚拟化。而且两者都是「不报错就成功了」,因此问题只会在写入的位置与读取的位置产生错位的那一刻才会暴露出来,也就是 64位迁移或更换安装程序的时机。
本文面向 Windows 业务应用的开发者,基于官方文档的依据,梳理 WOW64 注册表重定向的机制、被重定向的键与共享键的整理、UAC 注册表虚拟化的触发条件、安装程序与 COM 注册中出现的实际危害,以及在 C#/C++/reg.exe 中明确指定视图的正确写法。
1. 先说结论
- 64位 Windows 的注册表存在 64位与 32位两种视图,32位进程对
HKLM\Software的访问,会被注册表重定向器透明地引导到物理位置HKLM\Software\Wow6432Node。应用侧看不到任何错误或警告。1 - Wow6432Node 这个物理位置属于系统保留区域。不要把路径写死后直接访问(Windows 10 on ARM 上,32位 ARM 应用使用的是另一个名为
WowAA32Node的位置)。访问其他视图请使用官方手段(后文介绍的标志或 RegistryView)。12 - 只有部分键会被重定向,也有一些键是共享的。在 Windows 7 及以后版本中,
HKLM\SOFTWARE是「重定向」,HKLM\SOFTWARE\Classes是「共享」,但其下的CLSID与Interface又是「重定向」,呈嵌套结构。3 - 访问其他视图的方式是: 在 Win32 中,向
RegOpenKeyEx等函数的samDesired指定KEY_WOW64_64KEY/KEY_WOW64_32KEY;在 .NET 中,向RegistryKey.OpenBaseKey指定RegistryView.Registry64/Registry32;在命令行中,使用reg.exe的/reg:64//reg:32。245 - 没有管理员权限的 32位交互式进程写入
HKLM\Software时,可能会被 UAC 注册表虚拟化转发到HKEY_USERS\<SID>_Classes\VirtualStore\Machine\Software。写入操作会「成功」,而本人读回时看到的是合并视图,这正是陷阱所在。6 - 在清单中声明
requestedExecutionLevel后,文件/注册表虚拟化就会被禁用。反过来说,只有没有清单的遗留 EXE 才会成为虚拟化的对象。7 - 注册表虚拟化是一项过渡性的兼容技术,Microsoft 明确表示有意在未来的 Windows 版本中将其移除。新开发的应用不应依赖它。正确的设计是「不向 HKLM 写入」。6
- COM 的 CLSID 注册(
HKCR\CLSID=HKLM\Software\Classes\CLSID)会按位数分开。32位 COM 服务器的注册对 64位客户端不可见,是「未注册类(0x80040154)」的常见原因。3
两个机制的全貌
本文涉及的两个机制,在谁・做什么・转发到哪里这几点上是完全不同的东西。重定向是「位数层面的事」,对读写都会生效;虚拟化是「权限层面的事」,只对写入生效。两者很容易被混淆,在进入细节之前,先用一张图把全貌抓住。
flowchart TD
S["进程操作 HKLM 的 Software 之下"] --> B{"进程是32位吗"}
B -- "32位" --> R["WOW64注册表重定向<br/>位数层面的事、对读写都生效"]
B -- "64位" --> N["不重定向<br/>直接读写原始的 HKLM Software"]
R --> RV["实际读写的是<br/>Wow6432Node 之下 = 32位视图"]
RV --> W{"写入时,对该键<br/>没有写入权限"}
N --> W
W -- "没有清单的32位交互式进程" --> V["UAC注册表虚拟化<br/>权限层面的事、只对写入生效"]
V --> VS["转发到用户各自的 VirtualStore<br/>读取时是与原本位置的合并视图"]
W -- "64位进程、服务、有清单" --> E["以访问拒绝直接失败"]
该图左侧的分支(谁是32位)对应第2、3章的内容,右下方的分支(权限与清单)对应第5章的内容。
2. WOW64 注册表重定向 ── 32位进程看到的到底是哪里
64位 Windows 会把 32位应用运行在名为 WOW64 的子系统之上。这时注册表重定向器会分别向 32位进程和 64位进程展示各自不同的逻辑视图。两者使用相同的 API、指定相同的键名(HKEY_LOCAL_MACHINE\Software\...),但实际读写的物理位置却不同——这正是该机制的核心。1
被重定向的键,其物理位置就是 Wow6432Node。例如 32位进程的 HKEY_LOCAL_MACHINE\Software,物理上会被映射到 HKEY_LOCAL_MACHINE\Software\Wow6432Node。这一映射对应用完全透明,32位应用可以「以为自己仍在 32位 Windows 上运行」那样来操作注册表。1
把「谁看到的是哪里」整理成一览表如下。
| 访问方 | 代码中指定的路径 | 实际读写的物理位置 |
|---|---|---|
| 64位进程 | HKLM\Software\MyApp |
HKLM\Software\MyApp |
| 32位进程 | HKLM\Software\MyApp |
HKLM\Software\Wow6432Node\MyApp |
32位进程 + KEY_WOW64_64KEY(RegistryView.Registry64) |
HKLM\Software\MyApp |
HKLM\Software\MyApp |
64位进程 + KEY_WOW64_32KEY(RegistryView.Registry32) |
HKLM\Software\MyApp |
HKLM\Software\Wow6432Node\MyApp |
| 32位交互式进程、标准权限、无清单的写入 | HKLM\Software\MyApp |
HKCU\Software\Classes\VirtualStore\Machine\Software\Wow6432Node\MyApp(经 WOW64 重定向后再被虚拟化,参见第5章) |
| regedit(64位进程) | ─ | 以64位视图为基准显示,Wow6432Node 也会作为物理键原样可见 |
开头那个案例正是这张表的写照。32位安装程序写入的值,物理上位于 Wow6432Node 之下,而 64位化后的应用读取的是原始的 HKLM\Software。regedit 两边都能看到,所以「regedit 里有」的现场反馈,和「应用读不到」的现象,可以毫无矛盾地同时成立。
这时候,会有人想在代码中把 Wow6432Node 的路径直接写死来解决问题,但这是官方文档明确禁止的反模式。文档指出,重定向目标的物理位置属于系统保留区域,可能会发生变更。实际上,在 Windows 10 on ARM 上,32位 ARM 应用的重定向目标是另一个名为 WowAA32Node 的键。12 如果想读取其他视图,请使用第4章介绍的官方手段。
再补充一个细节: WOW64 还会对 32位应用写入的、以 %ProgramFiles% 开头的 REG_SZ/REG_EXPAND_SZ 字符串进行修正,将其替换为 %ProgramFiles(x86)%(仅在大小写也完全一致时才生效)。1 另外,在 Vista/XP 时代曾有一种在 32位/64位视图之间复制并同步键的「注册表反射(registry reflection)」机制,但它已在 Windows 7 / Windows Server 2008 R2 中被废除。阅读较早期的解说文章时,请留意这一前提上的差异。1
3. 被重定向的键、被共享的键
并非所有键都会被重定向,部分键会在两个视图之间共享同一份物理副本。以下从官方文档「Registry Keys Affected by WOW64」的列表中,摘录与业务应用开发关系密切的部分(Windows 7 / Server 2008 R2 及以后的一列。子键原则上会继承父键的行为)。3
| 键 | Windows 7 及以后的处理 |
|---|---|
HKLM\SOFTWARE |
重定向 |
HKLM\SOFTWARE\Classes |
共享 |
HKLM\SOFTWARE\Classes\CLSID |
重定向 |
HKLM\SOFTWARE\Classes\Interface |
重定向 |
HKLM\SOFTWARE\Classes\DirectShow / Media Type / MediaFoundation |
重定向 |
HKLM\SOFTWARE\Clients |
共享 |
HKLM\SOFTWARE\Microsoft\COM3 / EventSystem / OLE / RPC |
共享 |
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths |
共享 |
HKLM\SOFTWARE\Policies |
共享 |
HKCU\SOFTWARE |
共享 |
HKCU\SOFTWARE\Classes |
共享 |
HKCU\SOFTWARE\Classes\CLSID / Interface |
重定向 |
值得注意的是这种嵌套结构: HKLM\SOFTWARE 会被重定向,但其下的 Classes 又变回共享,而 Classes 之下的 CLSID 和 Interface 再次被重定向。如果只是笼统地理解为「Software 之下全都会跑到 Wow6432Node」,就无法解释文件扩展名关联(Classes 直属,共享)与 COM 类注册(Classes\CLSID,重定向)之间行为上的差异。HKCU 基本上是共享的,因此只要把用户级别的设置放在 HKCU 里,几乎就不会遇到位数问题——这也是一个重要的实务结论。3
另外,KEY_WOW64_64KEY 等标志对共享键不起作用。因为共享键本来就只有一份,切换视图没有意义。2
4. 显式读取其他视图 ── reg.exe、C#、C++ 的正确写法
要确认「值到底在哪个视图里」,最快的方法是使用 reg.exe 的 /reg:64 / /reg:32 选项,分别显式访问 64位视图和 32位视图。5
:: 读取64位视图(原始的HKLM\Software)
reg query "HKLM\SOFTWARE\KomuraSoft\DeviceLink" /v LicenseKey /reg:64
:: 读取32位视图(物理上位于Wow6432Node之下)
reg query "HKLM\SOFTWARE\KomuraSoft\DeviceLink" /v LicenseKey /reg:32
执行这两行命令后,如果只有一边能读到值,就可以确定「写入方与读取方的位数不一致」。要点在于,不是在路径里手写 Wow6432Node,而是针对同一个逻辑路径只切换视图。
在 C#(.NET)中,向 RegistryKey.OpenBaseKey 传入 RegistryView 即可。共有 Registry64(值256)、Registry32(值512)、Default(值0)三种,可以在 OpenBaseKey / OpenRemoteBaseKey / FromHandle 中指定。4
using Microsoft.Win32;
// 即使是32位进程,也读取64位视图
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
using (var key = baseKey.OpenSubKey(@"SOFTWARE\KomuraSoft\DeviceLink"))
{
var license = key?.GetValue("LicenseKey") as string;
}
// 从64位进程读取32位视图(Wow6432Node一侧)
// ── 用于迁移读取32位时代安装程序写入的值等场景
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
using (var key = baseKey.OpenSubKey(@"SOFTWARE\KomuraSoft\DeviceLink"))
{
var legacy = key?.GetValue("LicenseKey") as string;
}
RegistryView.Default 完全交由进程自身的位数决定。AnyCPU 构建的 .NET 应用会因运行环境不同而在 32位/64位之间变化,因此如果要处理 HKLM 之下面向整台机器共享的数据,在代码中明确指定要读取哪个视图,就能避免因构建设置变更(切换 Prefer 32-bit 或进行 64位迁移)导致注册表的可见状态突然改变这类事故。另外,在 32位操作系统上请求 Registry64 时,按规范会返回 32位视图的键,因此即便仍需支持 32位操作系统,同一份代码也能安全运行。4
在 C++(Win32 API)中,向 RegOpenKeyEx / RegCreateKeyEx / RegDeleteKeyEx 的 samDesired 用 OR 运算指定 KEY_WOW64_64KEY(0x0100)或 KEY_WOW64_32KEY(0x0200)。如果两者同时指定,会以 ERROR_INVALID_PARAMETER 失败。2
HKEY hKey = nullptr;
// 从32位进程打开64位视图的键
LSTATUS st = RegOpenKeyExW(
HKEY_LOCAL_MACHINE,
L"SOFTWARE\\KomuraSoft\\DeviceLink",
0,
KEY_READ | KEY_WOW64_64KEY, // 在此明确指定视图
&hKey);
if (st == ERROR_SUCCESS)
{
wchar_t buf[256]; DWORD cb = sizeof(buf); DWORD type = 0;
RegQueryValueExW(hKey, L"LicenseKey", nullptr, &type,
reinterpret_cast<LPBYTE>(buf), &cb);
RegCloseKey(hKey);
}
官方文档提到了两个注意点。一旦带着标志打开了某个其他视图,其下的子键操作(创建、删除、打开)也要持续显式指定相同的标志,混用会导致意料之外的行为。另外,如果想不遗漏地枚举两个视图中的键,需要分别用 KEY_WOW64_64KEY 打开的句柄和 KEY_WOW64_32KEY 打开的句柄,分两趟枚举。还要注意,RegDeleteKey(不带 Ex 的版本)无法访问其他视图。2
5. UAC 注册表虚拟化 ── 明明写入了 HKLM,值却出现在 VirtualStore
另一个容易与重定向混淆的机制,是UAC 的注册表虚拟化。这不是位数层面的事,而是权限层面的事,是从 Vista 开始为救济那些默认需要管理员权限的遗留应用而引入的一项兼容技术。6
运作方式是这样的: 没有写入权限的进程,尝试向 HKLM\Software 之下写入值或创建子键时,不会以访问拒绝失败,而是会把写入转发到用户各自的虚拟存储区 HKEY_USERS\<用户SID>_Classes\VirtualStore\Machine\Software(在 regedit 中表现为 HKCU\Software\Classes\VirtualStore\Machine\Software 之下)。而且读取时,返回的是虚拟存储区的值与原本全局存储区的值的合并视图,同名的值以虚拟存储区一侧为优先。6 另外,在 64位操作系统上的 32位进程中,第3章介绍的 WOW64 重定向会先生效,因此对 HKLM\Software\MyApp 的写入,实际转发目标会是 VirtualStore\Machine\Software\Wow6432Node\MyApp。调查 VirtualStore 之下的内容时,不要只看原始的 Software 一侧,也务必确认 Wow6432Node 一侧。
也就是说,从写入方自己的进程来看,读写就像什么都没发生过一样正常。这正是「在开发机(以管理员身份运行)上没问题,只有在客户的标准用户环境下设置才会出问题」「A 用户登录时能用,B 用户登录时却回到了初始值」这类因用户而异的诡异故障的真面目。虚拟存储区是用户配置文件(NTUSER.DAT 等)的一部分,因此每个用户的内容都不同(配置文件的结构请参见「Windows 用户配置文件入门 - AppData 与 NTUSER.DAT」)。
虚拟化生效的条件是有限的,官方文档中有明确说明。6
- 只有32位交互式进程对
HKLM\Software之下、管理员本可写入的键进行的操作才会成为对象 - 以下情况不在对象范围内: 64位进程、服务等非交互式进程、正在进行用户模拟(impersonation)的操作、驱动程序,以及清单中声明了
requestedExecutionLevel的进程 HKLM\Software\Classes、HKLM\Software\Microsoft\Windows、HKLM\Software\Microsoft\Windows NT之下也不在对象范围内
在实务中特别关键的一点是「有没有清单,行为就会不同」。Visual Studio 的 C++ 链接器默认会在清单中嵌入 asInvoker 的 UAC 片段7,因此用现代工具链构建出来的 EXE,从一开始就不在虚拟化的对象范围内。会遇到虚拟化的场景,是在 64位 Windows 上运行没有清单的 VB6/旧版 Delphi/旧版 VC++ 制作的遗留 EXE。反过来,如果对遗留 EXE「先加个清单再说」或「重新编译成 64位」,虚拟化就会立刻失效,这时反而会真正因访问拒绝而崩溃(或者悄悄写入失败)——这是迁移过程中的另一个陷阱。关于 requestedExecutionLevel 的三个取值(asInvoker / highestAvailable / requireAdministrator)以及权限设计的思路,在「Windows 什么时候需要管理员权限 - UAC、保护区域与设计上的辨别方式」中有详细说明。
需要特别强调的是,官方文档明确写明虚拟化是一项过渡性(interim)的兼容技术,且有意在未来的 Windows 中将其移除。在新开发中依赖这一行为是不可取的,设计原则应该是「应用不写入敏感的系统区域(HKLM)。数据应放在用户各自的位置,或放在设置了合适 ACL 的公共位置」。6 关于该保存在哪里、保存什么的判断方法,整理在「Windows应用的数据存储位置怎么选」一文中。
另外,还有一组可以按键控制虚拟化的标志(REG_KEY_DONT_VIRTUALIZE / REG_KEY_DONT_SILENT_FAIL / REG_KEY_RECURSE_FLAG),可以通过 reg.exe 的 flags 选项进行查询和设置。官方文档中给出的查询示例及其输出如下。6
C:\>reg flags HKLM\Software\AppKey1 QUERY
HKEY_LOCAL_MACHINE\Software\AppKey1
REG_KEY_DONT_VIRTUALIZE: CLEAR
REG_KEY_DONT_SILENT_FAIL: CLEAR
REG_KEY_RECURSE_FLAG: CLEAR
The operation completed successfully.
三项全部为 CLEAR,意味着「标志未被设置」= 该键上的虚拟化按默认方式生效。将各标志设置(SET)后的效果如下。6
| 标志 | 设置后的效果 |
|---|---|
REG_KEY_DONT_VIRTUALIZE |
禁用写入的虚拟化。权限不足的键创建・值设置不会被转发到 VirtualStore,而是直接失败 |
REG_KEY_DONT_SILENT_FAIL |
禁用打开的虚拟化。不再对权限不足的打开操作以 MAXIMUM_ALLOWED 重新打开来进行救济,而是使其失败 |
REG_KEY_RECURSE_FLAG |
使虚拟化标志从父键向子键传播。只对设置之后新建的子键生效,不适用于已存在的子键 |
也就是说,如果想「只让这个键停止虚拟化,把权限不足的问题暴露出来」,就可以设置 REG_KEY_DONT_VIRTUALIZE。设置标志时使用 SET 代替 QUERY,具体的选项排列方式请用 reg flags /? 确认。
排查故障时,有两个快捷手段: 一是在任务管理器的「详细信息」标签页中,右键点击列标题,从「选择列」中添加「UAC 虚拟化」,来查看各进程的虚拟化状态;二是查看 HKCU\Software\Classes\VirtualStore 之下,看有没有堆积被转发过去的残留内容。
6. 安装程序与 COM 注册中出现的实际危害
这两个机制最容易以实际危害的形式爆发出来的地方,就是安装程序和 COM 注册。
安装程序的位数问题。32位安装程序(32位 MSI 或 32位安装 EXE)写入 HKLM\Software\公司名 的设置,物理上会进入 Wow6432Node 之下。如果把应用本体升级为 64位,却仍沿用 32位安装程序,就会像开头的案例那样,凑成「安装程序写入了,应用却读不到」的组合。反过来的模式(64位安装程序 + 32位应用)也是同样的道理。对策是让安装程序与应用本体在「写入哪个视图、读取哪个视图」上保持一致,并在 64位迁移的过渡期,用第4章介绍的 RegistryView.Registry32 实现从旧位置进行迁移读取。
COM 注册的位数问题。如第3章表格所示,HKLM\Software\Classes\CLSID(即 HKCR\CLSID 的 HKLM 一侧)是重定向对象。也就是说,32位 COM 服务器的 CLSID 注册进入32位视图,64位的注册进入64位视图,二者互不可见。3 由于 64位进程本来就无法加载 32位 DLL 的进程内 COM 服务器,这种分离本身是合理的,但在现场往往会以「用 regsvr32 注册过了,客户端却报 0x80040154(未注册类)」的形式袭来。regsvr32 本身也分 32位版(SysWOW64 一侧)和 64位版(System32 一侧),用哪一个注册决定了写入哪个视图。COM 注册与位数组合所带来的陷阱全貌,整理在「COM/OCX/ActiveX 开发中容易踩坑的注册与位数陷阱」中;而让注册表注册本身变得不再必要的方案,整理在「什么是 Reg-Free COM——无需注册使用 COM 的机制」中。
COM 的世界里还有一段历史渊源。在 Vista/XP 时代,CLSID 等是以「重定向 + 反射(两个视图之间的同步)」方式处理的,但 Windows 7 废除了反射机制,如今已经变成纯粹按视图分离。3 另外,系统中还定义了若干用于兼容的符号链接,例如 HKLM\SOFTWARE\Wow6432Node\Classes → HKLM\SOFTWARE\Classes\Wow6432Node,但这些是用来救济那些已经把 Wow6432Node 硬编码进去的既有应用的,新开发的应用不应该使用它们。3
另外,即便跨过了注册表的分离问题,DLL 本身的加载还有另一套名称解析规则等着你。排查「找不到」类问题时,也请参阅「Windows DLL 名称解析机制 - 搜索顺序与 SxS」。
7. 故障排查 ── 用 Procmon 查看「实际读取了哪里」
即便了解了这些机制,面对眼前的故障,要确定「这个进程到底读取了哪个物理键」,还是需要实际观测。这时最有力的工具就是 Sysinternals 的 Process Monitor(Procmon)。
步骤很简单。
- 启动 Procmon,在筛选器中添加
Process Name is <目标应用>.exe - 在工具栏中把显示范围收窄为仅注册表操作(也可以用
Operation begins with Reg这个筛选条件) - 重现应用出问题的操作,查看
RegOpenKey/RegQueryValue/RegSetValue这些行
要点在于,Procmon 的 Path 列显示的是重定向解析之后的物理路径。即便 32位应用自以为打开的是 HKLM\Software\MyApp,在 Procmon 上显示的也会是 HKLM\SOFTWARE\WOW6432Node\MyApp。如果这里排列着一串 NAME NOT FOUND,「哪个视图的哪个键不存在」就一目了然;如果写入流向了 HKCU\Software\Classes\VirtualStore\...,也能观测到虚拟化的发动。要排查 COM 的 0x80040154,甚至可以追踪到 CLSID\{...} 的打开失败究竟发生在哪一个视图。关于 Procmon 的筛选器设计与解读方法的详情,请参阅「Process Monitor(ProcMon)实战指南 —— 10 分钟定位「配置未生效」「ACCESS DENIED」问题」。
8. 实务定式(判断表)
| 情况 | 该做的事 | 原因・补充 |
|---|---|---|
| 「regedit 里能看到,应用却看不到」 | 用 reg query ... /reg:64 与 /reg:32 分别读取两个视图进行比较 |
先确定值究竟在哪个视图里5 |
| 自己开发的 32位/64位进程都要读取同一个 HKLM 设置 | 在写入方固定视图(例如: 64位视图),读取方全部明确指定同一个视图 | 用 RegistryView.Registry64 / KEY_WOW64_64KEY 统一42 |
想在代码里直接写死 Wow6432Node |
不要这样做,改用指定视图的 API | 物理位置属于系统保留,在 ARM 上会变成 WowAA32Node12 |
| 用户级别设置的存放位置 | 放在 HKCU(或 AppData) | HKCU 是共享键,没有位数问题,也没有权限问题3 |
| 遗留 32位应用的设置「因用户而异」 | 检查 HKCU\Software\Classes\VirtualStore |
这是虚拟化转发的值按用户各自堆积的典型模式6 |
| 为遗留 EXE 添加清单/进行 64位化 | 先排查清楚 HKLM 写入的位置再动手 | 虚拟化会失效,此前「能正常工作」的写入会开始失败67 |
| COM 的 0x80040154 | 确认客户端与服务器的位数,用对应的 regsvr32/视图确认注册情况 | CLSID 注册是按视图分离的3 |
| 无法确定读取了哪里 | 用 Procmon 观测物理路径与结果(如 NAME NOT FOUND 等) | 停止猜测、直接看事实才是最快的方法 |
9. 总结
- 64位 Windows 的注册表存在两个视图,32位进程的
HKLM\Software会被透明地重定向到 Wow6432Node,这是「写入的值找不到了」问题的头号嫌疑对象。 - 重定向并非对所有键都生效。请记住这样的嵌套结构:
Classes共享,其下的CLSID/Interface重定向,HKCU 几乎全部共享。 - 严禁在代码里写死 Wow6432Node。请使用 reg.exe 的
/reg:64/reg:32、.NET 的RegistryView、Win32 的KEY_WOW64_64KEY/KEY_WOW64_32KEY来明确指定视图。 - UAC 注册表虚拟化会把没有清单的 32位交互式进程因权限不足而对 HKLM 的写入,悄悄转发到 VirtualStore。这是救济遗留应用的过渡性技术,新开发的应用不应依赖它。
- 安装程序与应用、COM 服务器与客户端,要在「使用哪个视图」上保持一致。进行 64位迁移时,应把从旧视图迁移读取的逻辑纳入设计。
- 拿不准的时候,就用 Procmon 观测物理路径。观测比猜测更快、更可靠。
相关文章
- COM/OCX/ActiveX 开发中容易踩坑的注册与位数陷阱
- 什么是 Reg-Free COM——无需注册使用 COM 的机制
- Windows 什么时候需要管理员权限 - UAC、保护区域与设计上的辨别方式
- Windows 用户配置文件入门 - AppData 与 NTUSER.DAT
- Windows应用的数据存储位置怎么选 ── SQLite / JSON / 注册表 / Access 判断表
- Process Monitor(ProcMon)实战指南 —— 10 分钟定位「配置未生效」「ACCESS DENIED」问题
- Windows DLL 名称解析机制 - 搜索顺序与 SxS
相关咨询领域
合同会社小村软件承接「注册表里写入的值读不到」「COM 注册找不到」这类故障的调查,32位应用・COM 组件资产的 64位迁移设计,以及包括设备联动软件在内的 Windows 业务应用的委托开发。
参考链接
-
Microsoft Learn, Registry Redirector。关于注册表重定向器为 32位/64位应用提供各自不同的逻辑视图且对应用透明、
HKEY_LOCAL_MACHINE\Software被重定向到HKEY_LOCAL_MACHINE\Software\Wow6432Node、物理位置属于系统保留区域且应用不应直接访问、Windows 10 on ARM 的 32位 ARM 键会映射到 WowAA32Node、%ProgramFiles%字符串的替换、反射机制已在 Windows 7 / Windows Server 2008 R2 中被废除等内容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Microsoft Learn, Accessing an Alternate Registry View。关于
KEY_WOW64_64KEY(0x0100)与KEY_WOW64_32KEY(0x0200)的含义、在RegCreateKeyEx・RegDeleteKeyEx・RegOpenKeyEx的samDesired中指定、两个标志同时指定会导致ERROR_INVALID_PARAMETER、对共享键不起作用、子键操作应持续使用相同标志、枚举全部键需要分两趟进行、Wow6432Node/WowAA32Node属于保留键等内容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Microsoft Learn, Registry Keys Affected by WOW64。关于被重定向的键与被共享的键的一览(Windows 7 及以后,
HKLM\SOFTWARE重定向,HKLM\SOFTWARE\Classes共享,Classes\CLSID・Interface・DirectShow等重定向,Clients・COM3・OLE・RPC・App Paths・Policies・HKCU\SOFTWARE等共享)、子键会继承父键的行为、HKCR是 HKLM 与 HKCU 中Classes的合并视图、包含 Wow6432Node 在内的兼容用符号链接是为救济既有应用而存在、新开发的应用不应使用等内容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
Microsoft Learn, RegistryView Enum (Microsoft.Win32)。关于
RegistryView枚举的Default(0)・Registry64(256)・Registry32(512)、可以在OpenBaseKey・OpenRemoteBaseKey・FromHandle中指定视图、在 32位操作系统上请求 64位视图时会返回 32位视图的键等内容。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, reg query。关于
reg query命令中/reg:32以 32位注册表视图、/reg:64以 64位注册表视图访问键的选项说明。 ↩ ↩2 ↩3 -
Microsoft Learn, Registry Virtualization。关于对
HKLM\Software的写入会被重定向到HKEY_USERS\<User SID>_Classes\VirtualStore\Machine\Software、读取时返回以虚拟存储区优先的合并视图、虚拟化的对象仅限于 32位交互式进程且针对HKLM\Software之下且管理员本可写入的键、在 64位进程・服务・正在模拟用户的操作・声明了 requestedExecutionLevel 的进程・Classes等子键上无效、这是一项过渡性的兼容技术且有意在未来移除、应用不应依赖它、通过 reg flags 对REG_KEY_DONT_VIRTUALIZE等标志进行控制等内容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 -
Microsoft Learn, Application manifests。关于
requestedExecutionLevel元素中asInvoker・requireAdministrator・highestAvailable的含义、指定requestedExecutionLevel节点会禁用文件与注册表的虚拟化、如果为了向后兼容而想利用虚拟化则应省略该节点、Visual C++ 链接器默认会在清单中嵌入asInvoker的 UAC 片段等内容。 ↩ ↩2 ↩3
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows应用的数据存储位置怎么选 ── SQLite / JSON / 注册表 / Access 判断表
Windows桌面应用的数据该存在哪里、用什么格式保存?本文整理AppData/ProgramData的使用区分,以及SQLite、JSON文件、注册表、Access(.accdb)各自的优势与陷阱,并附判断表,从实务角度讲解防止数据损坏与位数问题等注意事项。
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
多线程实务最佳实践 C 语言篇 ── 以 Win32 API 的方式安全编写
C 语言 × Win32 的多线程有其定式:用 _beginthreadex 创建线程、SRW 锁与条件变量、Interlocked、以停止事件 + WaitForMultipleObjects 设计停止流程。本文还将梳理 TerminateThread 的危险性与 Dll...
多线程实务最佳实践 C++ 篇 ── 用 RAII 和 jthread 从结构上消除事故
C++ 的多线程是数据竞争会变成未定义行为的世界。本文梳理 std::thread 析构函数的陷阱、jthread 与 stop_token 的停止设计、scoped_lock 的死锁规避、atomic 的正确定位,直至与 Win32 同步 API 的使用区分。
多线程实务最佳实践 .NET 篇 ── 在增加线程之前应先确定的事
针对 .NET/C# 整理「立了线程之后,偶尔崩溃・卡死」的防范设计准则。内容涵盖不自行创建线程而改用 Task、减少共享可变状态、锁的纪律、基于 CancellationToken 的停止设计,直至 UI 线程的处理方式。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- 为什么用 regedit 能看到值,但应用读取时却显示不存在?
- 典型原因是应用与 regedit 所看到的注册表视图不同。64位 Windows 上的 regedit 是一个 64位进程,会以 64位视图为基准显示,其中也包含 Wow6432Node(32位视图的物理位置)。而 32位应用打开 HKLM\Software 时,会被 WOW64 的注册表重定向器引导到 Wow6432Node 一侧,因此只存在于 64位视图中的值就会被判定为「不存在」。首先确认 regedit 中看到的值的路径里是否包含 Wow6432Node,再用 reg query 的 /reg:64 与 /reg:32 分别读取两个视图进行比较,是最快的排查方法。
- 可以在代码中直接指定 Wow6432Node 的路径进行访问吗?
- 应当避免这样做。Microsoft 的官方文档明确指出,重定向目标的物理位置属于系统保留区域,未来可能发生变更,因此应用程序不应直接访问。实际上,在 Windows 10 on ARM 上,32位 ARM 应用使用的是另一个名为 WowAA32Node 的物理位置,把 Wow6432Node 写死在代码里的做法在 ARM 环境下会直接失效。如果需要访问其他视图,请使用 KEY_WOW64_64KEY/KEY_WOW64_32KEY 标志,或 .NET 中的 RegistryView 这类官方手段。
- 为什么本该写入 HKLM 的值,却出现在了 HKCU 的 VirtualStore 中?
- 这是 UAC 注册表虚拟化在起作用的结果。没有写入权限的 32位交互式进程,如果清单中没有声明 requestedExecutionLevel,却尝试写入 HKLM\Software 之下,就不会失败,而是会被转发到用户各自的虚拟存储区(HKEY_USERS\<SID>_Classes\VirtualStore\Machine\Software,在 regedit 中表现为 HKCU\Software\Classes\VirtualStore 之下)。该进程自己读回时,看到的是虚拟存储区与原本位置的合并视图,因此表面上看起来一切正常,但 64位进程或服务却看不到这个值,于是就变成了「不同用户设置不一样」这种诡异的故障。虚拟化是用于救济遗留应用的过渡性技术,新开发的应用不应依赖它。
- 在 C# 中该如何明确指定注册表的位数(32位/64位视图)?
- 向 RegistryKey.OpenBaseKey 传入 RegistryView 即可。指定 RegistryView.Registry64,即使是 32位进程也能读写 64位视图;指定 RegistryView.Registry32,即使是 64位进程也能读写 32位视图(Wow6432Node 一侧)。RegistryView.Default 则完全交由进程自身的位数决定,因此在 AnyCPU 这类运行环境不同、位数也会随之改变的构建中,明确指定要读取哪一个视图会更安全。另外,在 32位操作系统上请求 Registry64 时会按规范返回 32位视图,因此即便还需要兼容 32位操作系统,同一份代码也能正常工作。
- 该如何禁用注册表虚拟化,或确认某进程是否处于虚拟化状态?
- 在应用一侧,只要在清单中声明 requestedExecutionLevel(哪怕是 asInvoker),该进程的文件/注册表虚拟化就会被禁用。在管理员一侧,可以用 reg flags 命令按键设置和查询 REG_KEY_DONT_VIRTUALIZE 标志。如果是排查已经在运行的应用,最快的办法是在任务管理器中查看「UAC 虚拟化」列来确认各进程的虚拟化状态,并检查 HKCU\Software\Classes\VirtualStore 之下是否堆积了被转发过去的值。作为长久对策,建议从设计上直接改为不向 HKLM 写入(把用户级别的设置放到 HKCU 或 AppData)。