104

我有为 AnyCPU 编译的 ac# 单元测试项目。我们的构建服务器是 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 之外的注册表?

4

6 回答 6

168

由于WOW64是一个 Windows 子系统,可以从 32 位应用程序中访问 64 位,因此可以读取 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。

而不是 using "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));

不要混淆,两个版本都用作第一个参数,您可以通过第二个参数(vs )Microsoft.Win32.RegistryHive.LocalMachine来区分是使用64 位还是32 位。RegistryView.Registry64RegistryView.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在评论中建议,我已经使用他的方法来开发一些有用的功能:在某些情况下,您想要取回所有密钥,无论它是 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)?.G‌​etValueNames();
}

public static IEnumerable<string> GetAllRegValueNames(string RegPath,
                                  RegistryHive hive = RegistryHive.LocalMachine) 
{
    var reg64 = GetRegValueNames(RegistryView.Registry64, RegPath, hive);
    var reg32 = GetRegValueNames(RegistryView.Re‌​gistry32, 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)?.G‌​etValue(ValueName);
}

public static object GetRegValue(string RegPath, string ValueName="",
                                 RegistryHive hive = RegistryHive.LocalMachine)
{   
    return GetRegValue(RegistryView.Registry64, RegPath, ValueName, hive) 
                     ?? GetRegValue(RegistryView.Re‌​gistry32, 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.Re‌​gistry32, 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 中的值名称和值的列表。

注意:如果省略上述相应函数中的参数,则可以访问键的默认值(由命令行工具显示REGEDT32.EXE为)。(Default)ValueName

要获取注册表项中的子键列表,请使用函数GetRegKeyNamesGetAllRegKeyNames。您可以使用此列表来遍历注册表中的其他键。

示例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 烦恼?因为如果你不在乎,你会更加头疼地找出为什么在你的代码中抛出空引用异常——你会花费大量时间来找出它发生的位置和原因。如果它发生在生产环境中,您将非常忙于研究日志文件或事件日志(我希望您已经实现了日志记录)......最好以防御的方式避免空问题。运算符?., ?[...可以]??您提供很多帮助(请参阅上面提供的代码)。有一篇很好的相关文章讨论了C# 中新的可为空引用类型,我建议阅读这篇文章,还有一篇关于 Elvis 运算符的文章。


提示:您可以使用免费版的Linqpad在 Windows 下测试所有示例。它不需要安装。不要忘记在命名空间导入选项卡中按下F4并输入Microsoft.Win32。在 Visual Studio 中,您需要using Microsoft.Win32;在代码的顶部。

提示:要熟悉新的null 处理运算符请在 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>"
}

用 .Net fiddle 试试

如果你有兴趣,这里有一些我放在一起的例子,展示了你可以用这个工具做些什么。

于 2012-11-05T12:42:39.997 回答
20

创建/打开注册表项时,您必须使用 KEY_WOW64_64KEY 参数。但是 AFAIK 无法通过 Registry 类实现,只有在直接使用 API 时才能实现。

可能有助于您入门。

于 2009-06-10T07:13:18.370 回答
6

我没有足够的代表发表评论,但值得指出的是,它在使用 OpenRemoteBaseKey 打开远程注册表时有效。添加 RegistryView.Registry64 参数可以让机器 A 上的 32 位程序访问机器 B 上的 64 位注册表。在我传递该参数之前,我的程序正在读取 OpenRemoteBaseKey 之后的 32 位,并没有找到我的密钥在之后。

注意:在我的测试中,远程机器实际上是我的机器,但我通过 OpenRemoteBaseKey 访问它,就像我访问另一台机器一样。

于 2015-01-13T17:31:59.297 回答
4

试试这个(来自 32 位进程):

> %WINDIR%\sysnative\reg.exe query ...

(发现here)。

于 2010-05-10T10:38:12.547 回答
4

如果不能将 .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);
    }
}
于 2016-04-27T09:42:53.203 回答
0

根据我阅读的内容和我自己的测试,在我看来,应该在此路径“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);
于 2020-05-11T08:35:45.210 回答