2009-06-10 108 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); 
    } 
} 
相關問題