2009-06-10 7 views
78

J'ai un projet de test d'unité C# compilé pour AnyCPU. Notre serveur de construction est une machine 64 bits et une instance SQL Express 64 bits est installée.Lecture du registre 64 bits à partir d'une application 32 bits

Le projet de test utilise un code similaire à ce qui suit pour identifier le chemin d'accès aux fichiers .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(); 
    } 

Ce code fonctionne très bien sur nos postes de travail 32bits, et a fait un travail correct sur le serveur de build jusqu'à ce que je récemment analyse de couverture de code activée avec NCover. Comme NCover utilise un composant COM 32 bits, le testeur (Gallio) s'exécute comme un processus 32 bits.

Vérification du Registre, il n'y a pas de clé "noms d'instance" sous

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Est-il possible pour une application en cours d'exécution en 32bit mode pour accéder au registre en dehors de Wow6432Node?

Répondre

16

vous devez utiliser le paramètre KEY_WOW64_64KEY lors de la création/ouverture de la clé de registre. Mais je pense que ce n'est pas possible avec la classe Registry, mais seulement lorsque vous utilisez l'API directement.

This peut vous aider à démarrer.

3

essayez ceci (à partir d'un processus de 32bit):

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

(constaté que here).

108

Il existe toujours un support natif pour l'accès au registre sous Windows 64 bits en utilisant .NET Framework 4.x. Le code suivant est testé avec   Windows 7, 64 bits   et également avec   Windows 10, 64 bits. Pour accéder au 64 registre de bits, vous pouvez utiliser:

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

Si vous souhaitez accéder au Registre 32bit, utilisez:

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

Ne pas confondre, les deux versions sont en utilisant Microsoft.Win32.RegistryHive.LocalMachine comme premier paramètre, vous faites la distinction si utiliser 64 bits ou 32 bits par le 2e paramètre (RegistryView.Registry64 contre RegistryView.Registry32).

Remarque que

  • Sur un Windows 64bit, HKEY_LOCAL_MACHINE\Software\Wow6432Node contient des valeurs utilisées par les applications 32 bits en cours d'exécution sur le système 64 bits. Seules les applications 64 bits authentiques stockent leurs valeurs directement dans HKEY_LOCAL_MACHINE\Software.Le sous-arbre Wow6432Node est entièrement transparent pour les applications 32 bits, les applications 32 bits voient toujours HKEY_LOCAL_MACHINE\Software comme ils s'y attendent (c'est une sorte de redirection). Dans les anciennes versions de Windows ainsi que Windows 7 bits 32 bits (et 32 ​​bits), le sous-arbre Wow6432Node existe évidemment et non. En raison d'un bogue dans Windows 7 (64 bits), la version du code source 32 bits renvoie toujours «Microsoft» quelle que soit l'organisation que vous avez enregistrée alors que la version du code source 64 bits renvoie la bonne organisation.

Pour en revenir à l'exemple que vous avez fourni, faites-le de la manière suivante pour accéder à la branche 64 bits:

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

Mise à jour:

I'D tiens à ajouter une approche intéressante Johny Skovdal a suggéré dans les commentaires, que j'ai ramassé pour développer des fonctions utiles en utilisant son approche: Dans certaines situations, vous wa nt pour récupérer toutes les clés, que ce soit 32 bits ou 64 bits. Les noms d'instance SQL sont un exemple. Vous pouvez utiliser une requête syndicale dans ce cas comme suit (C# 6 ou plus):

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

Maintenant, vous pouvez simplement utiliser les fonctions ci-dessus comme suit:

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

qui vous donnera une liste des noms de valeur et des valeurs dans sqlRegPath.

Notez la manipulation requis dans les fonctions car le serveur SQL peut être installé en tant que 32 bits ou 64 bits. Les fonctions sont surchargées de sorte que vous pouvez toujours passer le paramètre 32 bits ou 64 bits si nécessaire - cependant, si vous l'omettez, il essayera de lire 64 bits, si cela échoue (valeur nulle), il lit les valeurs de 32 bits.

Il y a une spécialité ici: Parce que GetAllRegValueNames est habituellement utilisé dans un contexte de boucle (voir exemple ci-dessus), elle retourne un vide dénombrable plutôt que null pour simplifier foreach boucles: si elle ne serait pas traitée de cette façon, la boucle devrait être préfixée par une instruction if vérifiant null ce qui serait fastidieux de le faire - donc cela est traité une fois dans la fonction.

Pourquoi s'embêter avec null? Parce que si vous ne vous en souciez pas, vous aurez beaucoup plus de maux de tête à découvrir pourquoi cette exception de référence nulle a été lancée dans votre code - vous passeriez beaucoup de temps à trouver où et pourquoi c'est arrivé. Et si cela s'est produit en production, vous serez très occupé à étudier les fichiers journaux ou les journaux d'événements (j'espère que vous avez implémenté la journalisation) ... mieux éviter les problèmes nuls où vous le pouvez d'une manière défensive. Les opérateurs ?., ?[ ... ] et ?? peuvent vous aider beaucoup (voir le code ci-dessus).


Conseil: Vous pouvez utiliser la version gratuite de Linqpad pour tester tous les exemples sous Windows. Il ne nécessite pas d'installation.N'oubliez pas d'appuyer sur F4 et d'entrer Microsoft.Win32 dans l'onglet Importation de l'espace de noms. Dans Visual Studio, vous avez besoin de using Microsoft.Win32; en haut de votre code.

Astuce: Pour vous familiariser avec les nouveaux opérateurs de manutention nuls , essayer (et debug) le code suivant dans 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>" 
} 

Si vous êtes intéressé, here sont quelques-uns exemples que j'ai mis ensemble montrant ce que vous pouvez faire d'autre avec l'outil.

+2

Merci pour cette réponse complète. De mémoire je pense que j'utilisais .NET 3.5 quand j'ai posté la question, mais bon de voir .NET 4 a amélioré la situation –

+2

De rien. J'ai récemment eu un problème similaire avec le registre 64 bits que j'avais déjà résolu et j'ai pensé qu'il valait la peine de partager la solution. – Matt

+2

C'est exactement ce que je cherchais. Je fais cela dans Windows 9.1 et ça marche très bien. –

4

Je n'ai pas suffisamment de rep pour commenter, mais il convient de souligner que cela fonctionne lors de l'ouverture d'un registre distant en utilisant OpenRemoteBaseKey. L'ajout du paramètre RegistryView.Registry64 permet à un programme 32 bits sur la machine A d'accéder au registre 64 bits sur la machine B. Avant de transmettre ce paramètre, mon programme lit le code 32 bits après OpenRemoteBaseKey et n'a pas trouvé la clé I était après.

Remarque: Lors de mon test, la machine distante était en fait ma machine, mais j'y ai accédé via OpenRemoteBaseKey, comme je le ferais pour une autre machine.

3

Si vous ne pouvez pas utiliser .NET 4 avec son RegistryKey.OpenBaseKey(..., RegistryView.Registry64), vous devez utiliser l'API Windows directement.

La Interop minimale est comme:

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

utiliser comme:

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