从 32 位应用程序读取 64 位注册表
Posted
技术标签:
【中文标题】从 32 位应用程序读取 64 位注册表【英文标题】:Reading 64bit Registry from a 32bit application 【发布时间】:2010-11-01 17:20:02 【问题描述】:我有一个为 AnyCPU 编译的 c# 单元测试项目。我们的构建服务器是 64 位机器,并安装了 64 位 SQL Express 实例。
测试项目使用类似于以下的代码来识别 .MDF 文件的路径:
private string GetExpressPath()
RegistryKey sqlServerKey = Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" );
string sqlExpressKeyName = (string) sqlServerKey.GetValue( "SQLEXPRESS" );
RegistryKey sqlInstanceSetupKey = sqlServerKey.OpenSubKey( sqlExpressKeyName + @"\Setup" );
return sqlInstanceSetupKey.GetValue( "SQLDataRoot" ).ToString();
这段代码在我们的 32 位工作站上运行良好,并且在构建服务器上运行良好,直到我最近使用 NCover 启用了代码覆盖率分析。因为 NCover 使用 32 位 COM 组件,所以测试运行程序 (Gallio) 作为 32 位进程运行。
检查注册表,下面没有“Instance Names”键
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server
有没有办法让运行在 32 位模式下的应用程序访问 Wow6432Node 之外的注册表?
【问题讨论】:
【参考方案1】:读取 64 位注册表是可能的,因为 WOW64 是一个 Windows 子系统,可从 32 位应用程序中访问 64 位。 (同样,在旧的基于 NT 的 Windows 版本中,它被称为 WOW,是 32 位 Windows 中的一个模拟层,以支持 16 位应用程序)。
在 64 位 Windows 下使用 .NET Framework 4.x 和较新的 .NET 版本(例如 .NET Core、.NET 5 和 6)仍然提供对注册表访问的原生支持.以下代码已使用 Windows 7、64 位 以及 Windows 10、64 位 进行测试。它也应该适用于 Windows 11。
您可以执行以下操作,而不是使用 "Wow6432Node"
,它通过将一个注册表树映射到另一个注册表树来模拟一个节点,使其虚拟地出现在那里:
决定是否需要访问 64 位或 32 位注册表,并按如下所述使用它。您还可以使用我稍后提到的代码(附加信息部分),它创建一个联合查询以在一个查询中从两个节点获取注册表项 - 因此您仍然可以使用它们的真实路径来查询它们。
64 位注册表
要访问64位注册表,您可以使用RegistryView.Registry64
,如下:
// using Microsoft.Win32
string value64 = string.Empty;
RegistryKey localKey =
RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine,
RegistryView.Registry64);
localKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
if (localKey != null)
value64 = localKey.GetValue("RegisteredOrganization").ToString();
localKey.Close();
Console.WriteLine(String.Format("RegisteredOrganization [value64]: 0",value64));
32 位注册表
如果您想访问32位注册表,请使用RegistryView.Registry32
,如下所示:
// using Microsoft.Win32
string value32 = string.Empty;
RegistryKey localKey32 =
RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine,
RegistryView.Registry32);
localKey32 = localKey32.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
if (localKey32 != null)
value32 = localKey32.GetValue("RegisteredOrganization").ToString();
localKey32.Close();
Console.WriteLine(String.Format("RegisteredOrganization [value32]: 0",value32));
不要混淆,两个版本都使用Microsoft.Win32.RegistryHive.LocalMachine
作为第一个参数,你可以通过来区分是使用64位还是32位 >第二个参数(RegistryView.Registry64
与 RegistryView.Registry32
)。
注意
在 64 位 Windows 上,HKEY_LOCAL_MACHINE\Software\Wow6432Node
包含在 64 位系统上运行的 32 位应用程序使用的值。只有真正的 64 位应用程序将它们的值直接存储在 HKEY_LOCAL_MACHINE\Software
中。子树Wow6432Node
对 32 位应用程序是完全透明的,32 位应用程序仍然可以看到 HKEY_LOCAL_MACHINE\Software
和他们期望的一样(这是一种重定向)。在旧版本的 Windows 以及 32 位 Windows 7(和 Vista 32 位)中,子树 Wow6432Node
显然不存在。
由于 Windows 7(64 位)中的错误,32 位源代码版本始终返回“Microsoft”,无论您注册了哪个组织,而 64 位源代码版本返回正确的组织。
回到您提供的示例,按照以下方式访问 64 位分支:
RegistryKey localKey =
RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine,
RegistryView.Registry64);
RegistryKey sqlServerKey = localKey.OpenSubKey(
@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL");
string sqlExpressKeyName = (string) sqlServerKey.GetValue("SQLEXPRESS");
附加信息 - 实际使用:
我想添加一个有趣的方法Johny Skovdal 在 cmets 中提出的建议,我已经使用他的方法来开发一些有用的功能:在某些情况下,您想要取回所有密钥,无论它是否是 32 位还是 64 位。 SQL 实例名称就是这样一个例子。在这种情况下,您可以使用联合查询,如下所示(C#6 或更高版本):
// using Microsoft.Win32;
public static IEnumerable<string> GetRegValueNames(RegistryView view, string regPath,
RegistryHive hive = RegistryHive.LocalMachine)
return RegistryKey.OpenBaseKey(hive, view)
?.OpenSubKey(regPath)?.GetValueNames();
public static IEnumerable<string> GetAllRegValueNames(string RegPath,
RegistryHive hive = RegistryHive.LocalMachine)
var reg64 = GetRegValueNames(RegistryView.Registry64, RegPath, hive);
var reg32 = GetRegValueNames(RegistryView.Registry32, RegPath, hive);
var result = (reg64 != null && reg32 != null) ? reg64.Union(reg32) : (reg64 ?? reg32);
return (result ?? new List<string>().AsEnumerable()).OrderBy(x => x);
public static object GetRegValue(RegistryView view, string regPath, string ValueName="",
RegistryHive hive = RegistryHive.LocalMachine)
return RegistryKey.OpenBaseKey(hive, view)
?.OpenSubKey(regPath)?.GetValue(ValueName);
public static object GetRegValue(string RegPath, string ValueName="",
RegistryHive hive = RegistryHive.LocalMachine)
return GetRegValue(RegistryView.Registry64, RegPath, ValueName, hive)
?? GetRegValue(RegistryView.Registry32, RegPath, ValueName, hive);
public static IEnumerable<string> GetRegKeyNames(RegistryView view, string regPath,
RegistryHive hive = RegistryHive.LocalMachine)
return RegistryKey.OpenBaseKey(hive, view)
?.OpenSubKey(regPath)?.GetSubKeyNames();
public static IEnumerable<string> GetAllRegKeyNames(string RegPath,
RegistryHive hive = RegistryHive.LocalMachine)
var reg64 = GetRegKeyNames(RegistryView.Registry64, RegPath, hive);
var reg32 = GetRegKeyNames(RegistryView.Registry32, RegPath, hive);
var result = (reg64 != null && reg32 != null) ? reg64.Union(reg32) : (reg64 ?? reg32);
return (result ?? new List<string>().AsEnumerable()).OrderBy(x => x);
现在你可以简单的使用上面的函数了:
示例 1: 获取 SQL 实例名称
var sqlRegPath=@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL";
foreach (var valueName in GetAllRegValueNames(sqlRegPath))
var value=GetRegValue(sqlRegPath, valueName);
Console.WriteLine($"valueName=value");
将为您提供 sqlRegPath 中的值名称和值的列表。
注意:如果省略ValueName
参数,您可以访问键的默认值(由命令行工具REGEDT32.EXE
显示为(Default)
)在上面的相应函数中。
要获取注册表项中的子键列表,请使用函数GetRegKeyNames
或GetAllRegKeyNames
。您可以使用此列表来遍历注册表中的其他键。
示例2:获取已安装软件的卸载信息
var currentVersionRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion";
var uninstallRegPath = $@"currentVersionRegPath\Uninstall";
var regKeys = Registry.GetAllRegKeyNames(RegPath: uninstallRegPath);
将获得所有 32 位和 64 位卸载密钥。
注意函数中需要的空值处理,因为 SQL Server 可以安装为 32 位或 64 位(上面的示例 1)。这些函数已重载,因此您仍然可以在需要时传递 32 位或 64 位参数 - 但是,如果您省略它,那么它将尝试读取 64 位,如果失败(空值),它会读取 32 位值。
这里有一个特点:因为 GetAllRegValueNames
通常用于循环上下文(参见上面的示例 1),它返回一个空的枚举而不是 null
以简化 foreach
循环:如果不这样处理,循环必须以if
语句为前缀,检查null
,这样做会很麻烦——所以在函数中处理一次。
为什么要为 null 烦恼?因为如果你不在乎,你会更加头疼地找出为什么在你的代码中抛出 null 引用异常 - 你会花很多钱时间找出它发生的地点和原因。如果它发生在生产中,您将非常忙于研究日志文件或事件日志(我希望您已经实现了日志记录)......最好以防御方式避免空问题。运算符?.
、?[
...]
和??
可以为您提供很多帮助(请参阅上面提供的代码)。有一篇很好的相关文章讨论了新的nullable reference types in C#,我建议您阅读这篇文章以及关于猫王运算符的this one。
提示:您可以使用免费版的Linqpad在Windows下测试所有示例。它不需要安装。不要忘记按 F4 并在命名空间导入选项卡中输入 Microsoft.Win32
。在 Visual Studio 中,您需要在代码顶部添加 using Microsoft.Win32;
。
提示:要熟悉新的 null handling operators,请在 LinqPad 中试用(并调试)以下代码:
示例 3: 演示 null 处理运算符
static string[] test get return null; // property used to return null
static void Main()
test.Dump(); // output: null
// "elvis" operator:
test?.Dump(); // output:
// "elvis" operator for arrays
test?[0].Dump(); // output:
(test?[0]).Dump(); // output: null
// combined with null coalescing operator (brackets required):
(test?[0]??"<null>").Dump(); // output: "<null>"
Try it with .Net fiddle
如果您有兴趣,here 是我整理的一些示例,展示了您可以使用该工具做什么。
【讨论】:
感谢您的全面回答。从记忆中我想我在发布问题时使用的是 .NET 3.5,但很高兴看到 .NET 4 改善了这种情况 不客气。我最近遇到了类似的 64 位注册表问题,我已经解决了,所以我认为值得分享解决方案。 这正是我一直在寻找的。我在 Windows 9.1 中这样做,效果很好。 @AZ_ - 感谢您的编辑,您是对的,需要关闭密钥! @JohnySkovdal - 我已更改标题以明确我只是提供额外(可选)信息 - 为那些想要深入研究此事的人提供。【参考方案2】:您必须在创建/打开注册表项时使用 KEY_WOW64_64KEY 参数。但是 AFAIK 无法通过 Registry 类实现,只有在直接使用 API 时才能实现。
This 可能会帮助您入门。
【讨论】:
【参考方案3】:我没有足够的代表发表评论,但值得指出的是,它在使用 OpenRemoteBaseKey 打开远程注册表时有效。添加 RegistryView.Registry64 参数可以让机器 A 上的 32 位程序访问机器 B 上的 64 位注册表。在我传递该参数之前,我的程序正在读取 OpenRemoteBaseKey 之后的 32 位,并没有找到我的密钥之后。
注意:在我的测试中,远程机器实际上是我的机器,但我通过 OpenRemoteBaseKey 访问它,就像我访问另一台机器一样。
【讨论】:
【参考方案4】:试试这个(来自 32 位进程):
> %WINDIR%\sysnative\reg.exe query ...
(发现here)。
【讨论】:
很好的提示,它允许批量操作注册表。使用reg.exe /?
获取更多信息...【参考方案5】:
如果您无法使用 .NET 4 及其 RegistryKey.OpenBaseKey(..., RegistryView.Registry64)
,则需要直接使用 Windows API。
最小的互操作是这样的:
internal enum RegistryFlags
...
RegSz = 0x02,
...
SubKeyWow6464Key = 0x00010000,
...
internal enum RegistryType
RegNone = 0,
...
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int RegGetValue(
UIntPtr hkey, string lpSubKey, string lpValue, RegistryFlags dwFlags,
out RegistryType pdwType, IntPtr pvData, ref uint pcbData);
像这样使用它:
IntPtr data = IntPtr.Zero;
RegistryType type;
uint len = 0;
RegistryFlags flags = RegistryFlags.RegSz | RegistryFlags.SubKeyWow6464Key;
UIntPtr key = (UIntPtr)((uint)RegistryHive.LocalMachine);
const string subkey= @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL";
const string value = "SQLEXPRESS";
if (RegGetValue(key, subkey, value, flags, out type, data, ref len) == 0)
data = Marshal.AllocHGlobal((int)len);
if (RegGetValue(key, subkey, value, flags, out type, data, ref len) == 0)
string sqlExpressKeyName = Marshal.PtrToStringUni(data);
【讨论】:
【参考方案6】:根据我阅读的内容和我自己的测试,在我看来,应该在此路径“SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall”中检查注册表。因为在其他路径中,卸载程序后不会删除寄存器。
通过这种方式,我得到了 64 个 32 位配置的寄存器。
string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey key = key64.OpenSubKey(registryKey);
if (key != null)
var list = key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName).GetValue("DisplayName")).ToList();
key.Close();
对于 32 个寄存器是:
registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
【讨论】:
以上是关于从 32 位应用程序读取 64 位注册表的主要内容,如果未能解决你的问题,请参考以下文章