Мною ранее была написана некоторая библиотека AcadInfo.dll, скомпилированная как AnyCPU. В ней, помимо прочего, определён метод, считывающий из реестра необходимую информацию обо всех установленных на локальной машине версиях программы AutoCAD (нередко их может быть установлено сразу несколько) и представляющий её в виде массива объектов AcadInfo, с которыми достаточно удобно работать (примеры в справке). Как правило, в наиболее свежих версиях AutoCAD разрядность установленного AutoCAD совпадает с разрядностью операционной системы, но так было не всегда и это следует учитывать. В данной заметке речь пойдёт о нюансах работы приложения с реестром операционной системы x64 в случаях, когда разрядность приложения является иной, т.е. x86...
ВНИМАНИЕ (добавлено 10 августа 2016)
Обновлённая версия опубликованного в этой заметке кода находится здесь.
Если пишется некое приложение, использующее, к примеру, обозначенный выше файл AcadInfo.dll и оно так же компилируется как AnyCPU, то информация об установленных AutoCAD успешно считывается из реестра. Однако не так давно обнаружил, что если такое приложение компилировать как x86 и запустить его в операционной системе x64, то в коде библиотеки AcadInfo.dll код выполняется так, как будто эта библиотека так же скомпилирована под x86. Это означает, что если AutoCAD, информацию о котором мы хотим прочитать в реестре, скомпилирован как x64, то в приложении x86 следующий код будет работать вовсе не так, как ожидалось (результат показан в комментарии):
В общем, потребовалось внести некоторые изменения в AcadInfo.dll дабы учесть подобного рода ситуации. Поскольку библиотеку AcadInfo.dll я компилирую под .NET 3.5 SP1 и в этой версии платформы отсутствует необходимый функционал, потребовалось его расширить. За основу взял код в одном зарубежном блоге (ссылка дана в комментариях кода ниже).
Исходный код вспомогательного, расширенного функционала по работе с реестром для .NET 3.5 SP1:
Внимание!
В обозначенном выше перегруженном методе OpenSubKey имеется маленький "артефакт": у возвращаемого объекта RegistryKey свойство Name возвращает пустую строку. Однако это не страшно, т.к. имя нам известно - первую часть можно получить из родителя, а вторую часть этого имени мы передаём в виде параметра.
Для того, чтобы в .NET 3.5 SP1 приложении можно было корректно определить разрядность как самого приложения, так и разрядность операционной системы, написан следующий код:
Пример использования:
Результат:
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
**********
Нажмите любую клавишу для завершения работы...
ВНИМАНИЕ (добавлено 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
**********
Нажмите любую клавишу для завершения работы...
Комментариев нет:
Отправить комментарий