การอ่าน 64 บิต Registry จากแอปพลิเคชัน 32 บิต


100

ฉันมีโครงการทดสอบหน่วย ac # ที่คอมไพล์สำหรับ AnyCPU บิลด์เซิร์ฟเวอร์ของเราเป็นเครื่อง 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 ใช้คอมโพเนนต์ COM 32 บิตนักวิ่งทดสอบ (Gallio) จึงทำงานเป็นกระบวนการ 32 บิต

ตรวจสอบรีจิสทรีไม่มีคีย์ "Instance Names" อยู่ข้างใต้

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

มีวิธีสำหรับแอปพลิเคชันที่ทำงานในโหมด 32 บิตเพื่อเข้าถึงรีจิสทรีภายนอก Wow6432Node หรือไม่

คำตอบ:


21

คุณต้องใช้พารามิเตอร์ KEY_WOW64_64KEY เมื่อสร้าง / เปิดคีย์รีจิสทรี แต่ AFAIK นั้นไม่สามารถทำได้กับคลาส Registry แต่เมื่อใช้ API โดยตรงเท่านั้น

วิธีนี้อาจช่วยให้คุณเริ่มต้นได้


152

ยังคงมีการสนับสนุนพื้นเมืองสำหรับการเข้าถึงรีจิสทรีภายใต้ 64 บิตของ Windows ใช้.NET Framework 4.x รหัสต่อไปนี้มีการทดสอบกับ   Windows 7 64 บิต   และยังมี   ของ Windows 10, 64 บิต

แทนที่จะใช้"Wow6432Node"ซึ่งเลียนแบบโหนดโดยการแมปรีจิสตรีทรีหนึ่งกับอีกอันหนึ่งทำให้ปรากฏขึ้นที่นั่นคุณสามารถทำการต่อไปนี้:

ตัดสินใจว่าคุณจำเป็นต้องเข้าถึงรีจิสทรี 64 บิตหรือ 32 บิตและใช้ตามที่อธิบายไว้ด้านล่าง นอกจากนี้คุณยังสามารถใช้รหัสที่ฉันกล่าวถึงในภายหลัง (ส่วนข้อมูลเพิ่มเติม) ซึ่งสร้างคิวรีแบบร่วมเพื่อรับคีย์รีจิสทรีจากทั้งสองโหนดในแบบสอบถามเดียวดังนั้นคุณยังสามารถสืบค้นได้โดยใช้เส้นทางจริง

รีจิสทรี 64 บิต

ในการเข้าถึงรีจิสทรี 64 บิตคุณสามารถใช้RegistryView.Registry64ดังนี้:

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ดังนี้:

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 บิตโดยพารามิเตอร์ที่ 2 ( RegistryView.Registry64เทียบกับRegistryView.Registry32)

โปรดทราบว่า

  • บน Windows 64 บิตHKEY_LOCAL_MACHINE\Software\Wow6432Nodeประกอบด้วยค่าที่ใช้โดยแอปพลิเคชัน 32 บิตที่ทำงานบนระบบ 64 บิต เฉพาะแอปพลิเคชัน 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พารามิเตอร์ในฟังก์ชันที่เกี่ยวข้องด้านบน

หากต้องการรับรายการSubKeysภายในคีย์รีจิสทรีให้ใช้ฟังก์ชันGetRegKeyNamesหรือGetAllRegKeyNames . คุณสามารถใช้รายการนี้เพื่อสำรวจคีย์เพิ่มเติมในรีจิสทรี

ตัวอย่างที่ 2: รับข้อมูลการถอนการติดตั้งของซอฟต์แวร์ที่ติดตั้ง

var currentVersionRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion";
var uninstallRegPath = $@"{currentVersionRegPath}\Uninstall";
var regKeys = Registry.GetAllRegKeyNames(RegPath: uninstallRegPath);

จะได้รับคีย์ถอนการติดตั้ง 32 บิตและ 64 บิตทั้งหมด

โปรดสังเกตว่าจำเป็นต้องมีการจัดการ nullในฟังก์ชันเนื่องจากเซิร์ฟเวอร์ SQL สามารถติดตั้งเป็น 32 บิตหรือ 64 บิต (ตัวอย่างที่ 1 ด้านบน) ฟังก์ชั่นมีการโหลดมากเกินไปดังนั้นคุณยังสามารถส่งผ่านพารามิเตอร์ 32 บิตหรือ 64 บิตได้หากจำเป็น - อย่างไรก็ตามหากคุณละเว้นมันจะพยายามอ่าน 64 บิตหากล้มเหลว (ค่า null) จะอ่านค่า 32 บิต

มีความพิเศษอย่างหนึ่งที่นี่: เนื่องจากGetAllRegValueNamesโดยปกติจะใช้ในบริบทการวนซ้ำ (ดูตัวอย่างที่ 1 ด้านบน) จะส่งคืนค่าว่างที่แจกแจงได้แทนที่จะnullทำให้foreachลูปง่ายขึ้น: หากไม่ได้รับการจัดการด้วยวิธีนี้การวนซ้ำจะต้องนำหน้าด้วย การifตรวจสอบคำสั่งnullซึ่งจะยุ่งยากหากต้องทำเช่นนั้น - เพื่อจัดการกับครั้งเดียวในฟังก์ชัน

ทำไมต้องกังวลเกี่ยวกับ null? เพราะถ้าคุณไม่สนใจคุณจะต้องปวดหัวมากขึ้นในการค้นหาว่าเหตุใดข้อยกเว้นการอ้างอิงที่เป็นโมฆะจึงถูกส่งเข้ามาในโค้ดของคุณคุณจะต้องใช้เวลามากมายในการค้นหาว่ามันเกิดขึ้นที่ไหนและทำไม และหากเกิดขึ้นในการใช้งานจริงคุณจะยุ่งมากกับการศึกษาไฟล์บันทึกหรือบันทึกเหตุการณ์ (ฉันหวังว่าคุณจะนำการบันทึกมาใช้) ... ดีกว่าหลีกเลี่ยงปัญหาว่างที่คุณสามารถป้องกันได้ ผู้ประกอบการ?., ?[... ]และ??สามารถช่วยให้คุณมาก (ดูรหัสที่ให้ไว้ด้านบน) มีบทความที่เกี่ยวข้องที่ดีเกี่ยวกับประเภทการอ้างอิงที่เป็นโมฆะใหม่ใน C #ซึ่งฉันแนะนำให้อ่านและบทความนี้เกี่ยวกับตัวดำเนินการ Elvis


คำแนะนำ:คุณสามารถใช้Linqpadรุ่นฟรีเพื่อทดสอบตัวอย่างทั้งหมดใน Windows ไม่จำเป็นต้องติดตั้ง อย่าลืมกดF4เข้าไปMicrosoft.Win32ในแท็บนำเข้าเนมสเปซ ใน Visual Studio คุณต้องการusing Microsoft.Win32;ที่ด้านบนของโค้ดของคุณ

เคล็ดลับ:การทำความคุ้นเคยกับใหม่ผู้ประกอบการจัดการ null ,ลอง (และการแก้ปัญหา) รหัสต่อไปนี้ใน LinqPad:

ตัวอย่างที่ 3: การสาธิตตัวดำเนินการจัดการกับค่าว่าง

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

หากคุณสนใจนี่คือตัวอย่างบางส่วนที่ฉันรวบรวมไว้ซึ่งแสดงให้เห็นว่าคุณสามารถใช้เครื่องมือนี้ทำอะไรได้อีกบ้าง


2
ขอบคุณสำหรับคำตอบที่ครอบคลุม จากความทรงจำฉันคิดว่าฉันใช้. NET 3.5 เมื่อฉันโพสต์คำถาม แต่ก็ดีที่ได้เห็นว่า. NET 4 ทำให้สถานการณ์ดีขึ้น
David Gardiner

2
ยินดีต้อนรับ เมื่อเร็ว ๆ นี้ฉันมีปัญหาคล้ายกันกับรีจิสทรี 64 บิตซึ่งฉันได้แก้ไขไปแล้วดังนั้นฉันคิดว่ามันคุ้มค่าที่จะแบ่งปันวิธีแก้ปัญหา
Matt

2
นี่คือสิ่งที่ฉันกำลังมองหา ฉันทำสิ่งนี้ใน windows 9.1 และใช้งานได้ดี
Michiel Bugher

1
@AZ_ - ขอบคุณสำหรับการแก้ไขคุณถูกต้องคีย์ต้องปิด!
Matt

1
@JohnySkovdal - ฉันเปลี่ยนหัวข้อข่าวเพื่อให้ชัดเจนฉันแค่ให้ข้อมูลเพิ่มเติม (ไม่บังคับ) - สำหรับผู้ที่ต้องการเจาะลึกลงไปในเรื่องนี้
Matt

6

ฉันมีตัวแทนไม่เพียงพอที่จะแสดงความคิดเห็น แต่ควรชี้ให้เห็นว่ามันใช้งานได้เมื่อเปิดรีจิสทรีระยะไกลโดยใช้ OpenRemoteBaseKey การเพิ่มพารามิเตอร์ RegistryView.Registry64 ช่วยให้โปรแกรม 32 บิตบนเครื่อง A เข้าถึงรีจิสทรี 64 บิตบนเครื่อง B ก่อนที่ฉันจะส่งผ่านพารามิเตอร์นั้นโปรแกรมของฉันกำลังอ่าน 32 บิตหลังจาก OpenRemoteBaseKey และไม่พบคีย์ I หลังจากนั้น

หมายเหตุ: ในการทดสอบของฉันเครื่องระยะไกลเป็นเครื่องของฉัน แต่ฉันเข้าถึงได้ผ่าน OpenRemoteBaseKey เช่นเดียวกับที่ฉันทำกับเครื่องอื่น


4

ลองสิ่งนี้ (จากกระบวนการ 32 บิต):

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

(พบว่าที่นี่ ).


1
คำใบ้ที่ดีช่วยให้สามารถจัดการกับรีจิสทรีเป็นชุดได้ ใช้reg.exe /?เพื่อรับข้อมูลเพิ่มเติม ...
แมตต์

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

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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.