пятница, 21 февраля 2014 г.

Работа с реестром в операционных системах Windows x64; разрядность системы и приложения

Мною ранее была написана некоторая библиотека AcadInfo.dll, скомпилированная как AnyCPU. В ней, помимо прочего, определён метод, считывающий из реестра необходимую информацию обо всех установленных на локальной машине версиях программы AutoCAD (нередко их может быть установлено сразу несколько) и представляющий её в виде массива объектов AcadInfo, с которыми достаточно удобно работать (примеры в справке). Как правило, в наиболее свежих версиях AutoCAD разрядность установленного AutoCAD совпадает с разрядностью операционной системы, но так было не всегда и это следует учитывать. В данной заметке речь пойдёт о нюансах работы приложения с реестром операционной системы x64 в случаях, когда разрядность приложения является иной, т.е. x86...


ВНИМАНИЕ (добавлено 10 августа 2016)
Обновлённая версия опубликованного в этой заметке кода находится здесь.

Если пишется некое приложение, использующее, к примеру, обозначенный выше файл AcadInfo.dll и оно так же компилируется как AnyCPU, то информация об установленных AutoCAD успешно считывается из реестра. Однако не так давно обнаружил, что если такое приложение компилировать как x86 и запустить его в операционной системе x64, то в коде библиотеки AcadInfo.dll код выполняется так, как будто эта библиотека так же скомпилирована под x86. Это означает, что если AutoCAD, информацию о котором мы хотим прочитать в реестре, скомпилирован как x64, то в приложении x86 следующий код будет работать вовсе не так, как ожидалось (результат показан в комментарии):

   1:  // parrentRegistry: HKEY_LOCAL_MACHINE
   2:  // ParrentAcadRegistryKey: @"SOFTWARE\Autodesk\AutoCAD"
   3:  regAcad = parrentRegistry.OpenSubKey(ParrentAcadRegistryKey, false); // null

В общем, потребовалось внести некоторые изменения в AcadInfo.dll дабы учесть подобного рода ситуации. Поскольку библиотеку AcadInfo.dll я компилирую под .NET 3.5 SP1 и в этой версии платформы отсутствует необходимый функционал, потребовалось его расширить. За основу взял код в одном зарубежном блоге (ссылка дана в комментариях кода ниже).

Исходный код вспомогательного, расширенного функционала по работе с реестром для .NET 3.5 SP1:

   1:  // dev.dll
   2:  // RegistryExtensions.cs
   3:  // © Андрей Бушман, 2014
   4:  // В файле RegistryExtensions.cs определён дополнительный функционал, 
   5:  // расширяющий возможности работы с реестром из .NET приложений, написанных на
   6:  // .NET 3.5 SP1.
   7:  #if !Net_4
   8:  using System;
   9:  using Microsoft.Win32;
  10:  using System.Reflection;
  11:  using System.Runtime.InteropServices;
  12:  using Microsoft.Win32.SafeHandles;
  13:   
  14:  /// В данном пространстве имён собран общий дополнительный функционал, который
  15:  /// может быть полезен при разработке любого .NET приложения.
  16:  namespace Bushman.Developing {
  17:      /// <summary>
  18:      /// Данный класс предназначен для предоставления 32-битным приложениям 
  19:      /// доступа к 64-битным разделам реестра. Класс так же может использоваться
  20:      /// для предоставления 64-битным приложениям ветке реестра, предназначенной
  21:      /// для 32-битных. За основу взят код, опубликованный в блоге 
  22:      /// http://clck.ru/96A9U
  23:      /// </summary>
  24:      public static class RegistryExtensions {
  25:          /// <summary>
  26:          /// Открытие ключа реестра, с указанием того, какую именно часть 
  27:          /// следует открывать: записи для 32-битных приложений, или же записи 
  28:          /// для 64-битных.
  29:          /// </summary>
  30:          /// <param name="parentKey">Родительский элемент RegistryKey, в котором
  31:          /// следует выполнить открытие подраздера.</param>
  32:          /// <param name="subKeyName">Name of the key to be opened</param>
  33:          /// <param name="writable">true - открывать для чтения и записи; 
  34:          /// false - открывать только для чтения.</param>
  35:          /// <param name="options">Какую именно часть реестра следует открывать:
  36:          /// относящуюся к 32-битным приложениям или же относящуюся к 64-битным.
  37:          /// </param>
  38:          /// <returns>Возвращается RegistryKey или null, если по каким-либо
  39:          /// причинам получить RegistryKey не удалось.</returns>
  40:          public static RegistryKey OpenSubKey(this RegistryKey parentKey,
  41:              String subKeyName, Boolean writable, RegWow64Options options) {
  42:              // Проверка работоспособности
  43:              if (parentKey == null || GetRegistryKeyHandle(parentKey) ==
  44:                  IntPtr.Zero) {
  45:                  return null;
  46:              }
  47:              // Назначение прав
  48:              Int32 rights = (Int32)(writable ? RegistryRights.WriteKey :
  49:                  RegistryRights.ReadKey);
  50:   
  51:              // Вызов функций неуправляемого кода
  52:              Int32 subKeyHandle, result = RegOpenKeyEx(GetRegistryKeyHandle(
  53:                  parentKey), subKeyName, 0, rights | (Int32)options,
  54:                  out subKeyHandle);
  55:   
  56:              // Если мы ошиблись - возвращаем null
  57:              if (result != 0) {
  58:                  return null;
  59:              }
  60:   
  61:              // Получаем ключ, представленный указателем, возвращённым из 
  62:              // RegOpenKeyEx
  63:              RegistryKey subKey = PointerToRegistryKey((IntPtr)subKeyHandle,
  64:                  writable, false);
  65:              return subKey;
  66:          }
  67:   
  68:          /// <summary>
  69:          /// Получить указатель на ключ реестра.
  70:          /// </summary>
  71:          /// <param name="registryKey">Ключ реестра, указатель на который нужно 
  72:          /// получить.
  73:          /// </param>
  74:          /// <returns>Возвращается объект IntPtr. Если не удалось получить 
  75:          /// указатель на обозначенный объект RegistryKey, то возвращается 
  76:          /// IntPtr.Zero.</returns>
  77:          public static IntPtr GetRegistryKeyHandle(this RegistryKey registryKey) {
  78:              if (registryKey == null) return IntPtr.Zero;
  79:              Type registryKeyType = typeof(RegistryKey);
  80:              System.Reflection.FieldInfo fieldInfo =
  81:              registryKeyType.GetField("hkey", BindingFlags.NonPublic |
  82:              BindingFlags.Instance);
  83:              // Получить дескриптор для поля hkey
  84:              SafeHandle handle = (SafeHandle)fieldInfo.GetValue(registryKey);
  85:              // Получить небезопасный дескриптор
  86:              IntPtr dangerousHandle = handle.DangerousGetHandle();
  87:              return dangerousHandle;
  88:          }
  89:   
  90:          /// <summary>
  91:          /// Получить ключ реестра на основе его указателя.
  92:          /// </summary>
  93:          /// <param name="hKey">Указатель на ключ реестра</param>
  94:          /// <param name="writable">true - открыть для записи; false - для 
  95:          /// чтения.</param>
  96:          /// <param name="ownsHandle">Владеем ли мы дескриптором: true - да, 
  97:          /// false - нет.</param>
  98:          /// <returns>Возвращается объект RegistryKey, соответствующий 
  99:          /// полученному указателю.</returns>
 100:          public static RegistryKey PointerToRegistryKey(IntPtr hKey,
 101:              Boolean writable, Boolean ownsHandle) {
 102:              BindingFlags privateConstructors = BindingFlags.Instance |
 103:                  BindingFlags.NonPublic;
 104:              Type safeRegistryHandleType =
 105:                  typeof(SafeHandleZeroOrMinusOneIsInvalid).Assembly
 106:                  .GetType("Microsoft.Win32.SafeHandles.SafeRegistryHandle");
 107:              // Получаем массив типов, соответствующих аргументом конструктора,
 108:              // который нам нужен
 109:              Type[] safeRegistryHandleCtorTypes = new Type[] { typeof(IntPtr), 
 110:                  typeof(Boolean) };
 111:              // Получаем ConstructorInfo для нашего объекта
 112:              System.Reflection.ConstructorInfo safeRegistryHandleCtorInfo =
 113:                  safeRegistryHandleType.GetConstructor(privateConstructors,
 114:                  null, safeRegistryHandleCtorTypes, null);
 115:              // Вызываем конструктор для SafeRegistryHandle.
 116:              // Класс SafeRegistryHandle появился в .NET 4.0
 117:              Object safeHandle = safeRegistryHandleCtorInfo.Invoke(
 118:                  new Object[] { hKey, ownsHandle });
 119:   
 120:              Type registryKeyType = typeof(RegistryKey);
 121:              // Получаем массив типов, соответствующих аргументом конструктора,
 122:              // который нам нужен
 123:              Type[] registryKeyConstructorTypes = new Type[] { 
 124:                  safeRegistryHandleType, typeof(bool) };
 125:              // Получаем ConstructorInfo для нашего объекта
 126:              System.Reflection.ConstructorInfo registryKeyCtorInfo =
 127:                  registryKeyType.GetConstructor(privateConstructors, null,
 128:                  registryKeyConstructorTypes, null);
 129:              // Вызываем конструктор для RegistryKey
 130:              RegistryKey resultKey = (RegistryKey)registryKeyCtorInfo.Invoke(
 131:                  new Object[] { safeHandle, writable });
 132:              // возвращаем полученный ключ реестра
 133:              return resultKey;
 134:          }
 135:   
 136:          /// <summary>
 137:          /// Получение числового значения указателя на искомый подраздел реестра.
 138:          /// </summary>
 139:          /// <param name="hKey">Указатель на родительский раздел реестра.</param>
 140:          /// <param name="subKey">Имя искомого подраздела.</param>
 141:          /// <param name="ulOptions">Этот параметр зарезервирован и всегда 
 142:          /// должен быть равным 0.</param>
 143:          /// <param name="samDesired">Права доступа (чтение\запись) и указание 
 144:          /// того, как именно следует открывать реестр. Значение этого параметра
 145:          /// формируется путём применения операции логического "И" для объектов
 146:          /// перечислений RegistryRights и RegWow64Options.</param>
 147:          /// <param name="phkResult">Ссылка на переменную, в которую следует 
 148:          /// сохранить полученное числовое значение указателя на искомый 
 149:          /// подраздел.</param>
 150:          /// <returns></returns>
 151:          [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
 152:          public static extern Int32 RegOpenKeyEx(IntPtr hKey, String subKey,
 153:              Int32 ulOptions, Int32 samDesired, out Int32 phkResult);
 154:      }
 155:      /// <summary>
 156:      /// Перечисление указывает, какую именно часть реестра следует открывать:
 157:      /// относящуюся к 32-битным приложениям или же относящуюся к 64-битным.
 158:      /// </summary>
 159:      public enum RegWow64Options {
 160:          /// <summary>
 161:          /// Открывать ту часть реестра, которая хранит информацию приложений,
 162:          /// разрядность которых соответствует разрядности текущего приложения
 163:          /// (x86\x64).
 164:          /// </summary>
 165:          None = 0,
 166:          /// <summary>
 167:          /// Открывать часть реестра, относящуюся к 64-битным приложениям.
 168:          /// </summary>
 169:          KEY_WOW64_64KEY = 0x0100,
 170:          /// <summary>
 171:          /// Открывать часть реестра, относящуюся к 32-битным приложениям.
 172:          /// </summary>
 173:          KEY_WOW64_32KEY = 0x0200
 174:      }
 175:      /// <summary>
 176:      /// Перечисление, указывающее на то, с каким уровнем доступа следует 
 177:      /// открывать ветку реестра: для чтения, или же для чтения\записи.
 178:      /// </summary>
 179:      public enum RegistryRights {
 180:          /// <summary>
 181:          /// Открыть только для чтения.
 182:          /// </summary>
 183:          ReadKey = 131097,
 184:          /// <summary>
 185:          /// Открыть для чтения и записи.
 186:          /// </summary>
 187:          WriteKey = 131078
 188:      }
 189:  }
 190:  #endif

Внимание!
В обозначенном выше перегруженном методе OpenSubKey имеется маленький "артефакт": у возвращаемого объекта RegistryKey свойство Name возвращает пустую строку. Однако это не страшно, т.к. имя нам известно - первую часть можно получить из родителя, а вторую часть этого имени мы передаём в виде параметра.

Для того, чтобы в .NET 3.5 SP1 приложении можно было корректно определить разрядность как самого приложения, так и разрядность операционной системы, написан следующий код:

   1:  using Microsoft.Win32;
   2:  // dev.dll
   3:  // Platform.cs
   4:  // © Андрей Бушман, 2014
   5:  // Код данного файла написан для удобства в использовании в .NET 3.5 SP1
   6:  using System;
   7:  using System.Diagnostics;
   8:  using System.Runtime.InteropServices;
   9:   
  10:  namespace Bushman.Developing {
  11:   
  12:      /// <summary>
  13:      /// Статический класс PlatformInfo предназначен для получения информации
  14:      /// о разрядности текущего приложения, а так же о разрядности операционной 
  15:      /// системы.
  16:      /// </summary>
  17:      public static class PlatformInfo {
  18:   
  19:          /// <summary>
  20:          /// Проверить разрядность указанного процесса (x86\x64).
  21:          /// </summary>
  22:          /// <param name="hProcess">Указатель проверяемого процесса</param>
  23:          /// <param name="lpSystemInfo">Ссылка на логическое значение, в котором 
  24:          /// будет сохранён результат.</param>
  25:          /// <returns>true - указанный процесс является x86; false - процесс x64.</returns>
  26:          [DllImport("kernel32.dll", SetLastError = true,
  27:              CallingConvention = CallingConvention.Winapi)]
  28:          [return: MarshalAs(UnmanagedType.Bool)]
  29:          public static extern Boolean IsWow64Process([In] IntPtr hProcess,
  30:              [Out] out Boolean lpSystemInfo);
  31:   
  32:          /// <summary>
  33:          /// Проверка на то, является ли текущий операционная система 64-битной.
  34:          /// </summary>
  35:          /// <returns>true - текущая операционная система x64, иначе - false.</returns>
  36:          public static Boolean Is64BitOS() {
  37:              Boolean isWow64 = false;
  38:              if ((Environment.OSVersion.Version.Major == 5 && Environment.OSVersion
  39:                  .Version.Minor >= 1) || Environment.OSVersion.Version.Major >= 6) {
  40:                  using (Process p = Process.GetCurrentProcess()) {
  41:                      Boolean retVal;
  42:                      if (!IsWow64Process(p.Handle, out retVal)) {
  43:                          isWow64 = false;
  44:                      }
  45:                      isWow64 = retVal;
  46:                  }
  47:              }
  48:              else {
  49:                  isWow64 = false;
  50:              }
  51:   
  52:              Boolean is64BitProcess = (IntPtr.Size == 8);
  53:              Boolean is64BitOperatingSystem = is64BitProcess || isWow64;
  54:              return is64BitOperatingSystem;
  55:          }
  56:   
  57:          /// <summary>
  58:          /// Получить разрядность текущего приложения
  59:          /// </summary>
  60:          /// <returns>Возвращается объект перечисления Platform.</returns>
  61:          public static Platform GetProcessPlatform() {
  62:              return IntPtr.Size == 4 ? Platform.x86 : Platform.x64;
  63:          }
  64:   
  65:          /// <summary>
  66:          /// Получить разрядность текущей операционной системы
  67:          /// </summary>
  68:          /// <returns>Возвращается объект перечисления Platform.</returns>
  69:          public static Platform GetOSPlatform() {
  70:              return Is64BitOS() ?
  71:                  Platform.x64 : Platform.x86;
  72:          }
  73:   
  74:      }
  75:      /// <summary>
  76:      /// Разрядность операционной системы или приложения.
  77:      /// </summary>
  78:      public enum Platform {
  79:          /// <summary>
  80:          /// Платформа x86
  81:          /// </summary>
  82:          x86,
  83:          /// <summary>
  84:          /// Платформа x64
  85:          /// </summary>
  86:          x64
  87:      }
  88:  }

Пример использования:

   1:  using System;
   2:  using Microsoft.Win32;
   3:  using Bushman.AutoCAD;
   4:  using Bushman.Developing;
   5:  using System.Runtime.InteropServices;
   6:  using System.Diagnostics;
   7:   
   8:  namespace ConsoleSample {
   9:      class Program {
  10:          static void Main(string[] args) {
  11:   
  12:              Platform appPlatform = PlatformInfo.GetProcessPlatform();
  13:              Platform osPlatform = PlatformInfo.GetOSPlatform();
  14:   
  15:              Console.WriteLine("OS: {0}\nApplication: {1}\n", osPlatform, appPlatform);
  16:   
  17:              // Открываем для чтения ветку реестра, разрядность которой соответствует 
  18:              // разрядности текущего приложения.
  19:              RegistryKey key = null;
  20:  #if !Net_4
  21:              key = Registry.LocalMachine.OpenSubKey(
  22:                  @"SOFTWARE\Autodesk", false, Bushman.Developing.RegWow64Options.None);
  23:  #else
  24:              RegistryKey localMachineX32View = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,
  25:                  RegistryView.Default);
  26:              key = localMachineX32View.OpenSubKey(@"SOFTWARE\Autodesk", false);
  27:  #endif
  28:              String[] names = key.GetSubKeyNames();
  29:              PrintStrings("RegWow64Options.None", names);
  30:   
  31:              // Открываем для чтения ветку реестра, относящуюся к 64-битным 
  32:              // приложениям.
  33:              key = null;
  34:  #if !Net_4
  35:              key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Autodesk", false,
  36:                  Bushman.Developing.RegWow64Options.KEY_WOW64_64KEY);
  37:  #else
  38:              localMachineX32View = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,
  39:                  RegistryView.Registry64);
  40:              key = localMachineX32View.OpenSubKey(@"SOFTWARE\Autodesk", false);
  41:  #endif
  42:   
  43:   
  44:              String[] names64 = key.GetSubKeyNames();
  45:              PrintStrings("RegWow64Options.KEY_WOW64_64KEY", names64);
  46:   
  47:              // Открываем для чтения ветку реестра, относящуюся к 32-битным 
  48:              // приложениям.
  49:              key = null;
  50:  #if !Net_4
  51:              key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Autodesk", false,
  52:                  Bushman.Developing.RegWow64Options.KEY_WOW64_32KEY);
  53:  #else
  54:              localMachineX32View = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,
  55:                  RegistryView.Registry32);
  56:              key = localMachineX32View.OpenSubKey(@"SOFTWARE\Autodesk", false);
  57:  #endif
  58:   
  59:   
  60:              String[] names32 = key.GetSubKeyNames();
  61:              PrintStrings("RegWow64Options.KEY_WOW64_32KEY", names32);
  62:   
  63:              Console.WriteLine("Нажмите любую клавишу для завершения работы...");
  64:              Console.ReadKey();
  65:          }
  66:   
  67:          /// <summary>
  68:          /// Вывод элементов текстового массива на печать.
  69:          /// </summary>
  70:          /// <param name="header">Заголовок вывода</param>
  71:          /// <param name="strings">Массив строк, которые необходимо вывести на
  72:          /// печать.
  73:          /// </param>
  74:          static void PrintStrings(String header, String[] strings) {
  75:              const Int32 count = 10;
  76:              Console.WriteLine(new String('*', count));
  77:              Console.WriteLine(header);
  78:              Console.WriteLine("Items count: {0}", strings.Length);
  79:              Console.WriteLine(new String('*', count));
  80:              foreach (String item in strings) {
  81:                  Console.WriteLine("\t{0}", item);
  82:              }
  83:              Console.WriteLine(new String('*', count));
  84:              Console.WriteLine();
  85:          }
  86:      }
  87:  }

Результат:

OS: x64
Application: x86

**********
RegWow64Options.None
Items count: 10
**********
        ADSKAssetLibrary
        ADSKTextureLibrary
        Common Install
        Content Service
        ContentService
        Licenses
        ObjectARX Wizards for AutoCAD Raptor
        ObjectDBX
        PLU26
        Structural
**********

**********
RegWow64Options.KEY_WOW64_64KEY
Items count: 15
**********
        AutoCAD
        Autodesk ReCap
        Autodesk Sync
        CSP
        Drawing Check
        Extensions
        Hardcopy
        Inventor
        Inventor Server SDK ACAD 2014
        LiveUpdate
        MC3
        ObjectDBX
        Revit
        UPI
        Workflow
**********

**********
RegWow64Options.KEY_WOW64_32KEY
Items count: 10
**********
        ADSKAssetLibrary
        ADSKTextureLibrary
        Common Install
        Content Service
        ContentService
        Licenses
        ObjectARX Wizards for AutoCAD Raptor
        ObjectDBX
        PLU26
        Structural
**********

Нажмите любую клавишу для завершения работы...

Комментариев нет: