2009-06-10 105 views
78

我有一个为AnyCPU编译的c#单元测试项目。我们的构建服务器是一个64位机器,并安装了64位SQL Express实例。从32位应用读取64位注册表

测试项目使用类似于下面的识别到.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位进程运行。

检查注册表,有下

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node没有 “实例名称” 键\微软\ Microsoft SQL Server的

是否有在32位运行的应用程序的方式模式访问Wow6432Node外的注册表?

回答

16

您在创建/打开注册表项时必须使用KEY_WOW64_64KEY参数。但是AFAIK对于注册表类来说是不可能的,但只能直接使用API​​。

This可能有助于帮助您入门。

3

试试这个(从32位进程):

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

(发现here)。

108

对于64位Windows下的注册表访问,仍然使用.NET Framework 4.x。以下代码使用  Windows 7,64位  以及  Windows 10,64位进行测试。 要访问64位注册表,你可以使用:

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(); 
} 
Console.WriteLine(String.Format("RegisteredOrganization [value64]: {0}",value64)); 

如果您要访问的32位注册表,用途:

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(); 
} 
Console.WriteLine(String.Format("RegisteredOrganization [value32]: {0}",value32)); 

不要被迷惑,两个版本使用Microsoft.Win32.RegistryHive.LocalMachine作为第一个参数,可以区分是使用64位还是32位第2参数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已在评论中提出,我已经选择使用他的方法来开发一些有用的功能:在某些情况下,您可以nt来取回所有密钥,不管它是32位还是64位。 SQL实例名称就是这样一个例子。你可以在这种情况下,使用一个联合查询如下(C#6或更高版本):

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); 
} 

现在,你可以简单地使用上述功能如下:

var [email protected]"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL"; 
foreach (var valueName in GetAllRegValueNames(sqlRegPath)) 
{ 
    var value=GetRegValue(sqlRegPath, valueName); 
    Console.WriteLine($"{valueName}={value}"); 
} 

,这将给你一个列表sqlRegPath中的值名称和值。

注意函数中需要空处理,因为SQL Server可以安装为32位或64位。函数被重载,所以如果需要的话你仍然可以传递32位或64位参数 - 但是,如果你省略了它,它将尝试读取64位,如果失败(空值),它读取32位值。

有一个专门在这里:因为GetAllRegValueNames在一个循环背景下通常使用(见上面的例子),它返回一个空的枚举,而不是null简化foreach循环:如果不处理这种方式,循环必须以if语句作为前缀,检查null,这样做会很麻烦 - 所以在函数中处理一次。

为什么要打扰null?因为如果你不在意,你会有更多的头痛发现为什么在你的代码中抛出空引用异常 - 你会花很多时间来找出它发生的地方和原因。如果它发生在生产环境中,你将会非常忙于学习日志文件或事件日志(我希望你已经实现了日志记录功能)......在防御方式下更好地避免空问题。运营商?.,?[ ... ]??可以帮助您很多(请参阅上面提供的代码)。


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

提示:为了与新空处理运营商熟悉,尝试(和调试)在LinqPad下面的代码:

string[] test { get { return null;} } // property used to return null 
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>" 
} 

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

+2

谢谢你提供的全面的答案。从内存中,我想我在发布这个问题时使用了.NET 3.5,但很高兴看到.NET 4改善了这种情况 – 2012-11-06 00:03:49

4

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

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

3

如果您不能使用.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); 
    } 
}