Реестр
45C# и .NET --- Многопоточность и файлы --- Реестр
Во всех версиях Windows, начиная с Windows 95, системный реестр является центральным хранилищем всей конфигурационной информации, касающейся настройки Windows, пользовательских предпочтений, а также установленного программного и аппаратного обеспечения. Почти любое коммерческое программное обеспечение в наши дни использует реестр для хранения информации о себе.
Компонентам СОМ тоже требуется сохранять информацию о себе в реестре для того, чтобы клиенты могли к ним обращаться. Среда .NET Framework и предлагаемая в ней концепция установки с нулевым воздействием (zeroimpact installation) делает реестр чуть менее существенным для приложений, поскольку создаваемые с ее помощью сборки получаются полностью самодостаточными, т.е. никакой информации в реестр для них помещать не требуется, даже если они являются разделяемыми.
Вдобавок в .NET Framework предлагается концепция изолированного хранилища (приложения могут хранить информацию, касающуюся каждого пользователя, в файлах) и .NET Framework самостоятельно заботится об отдельном хранении данных для каждого зарегистрированного в системе пользователя.
Тот факт, что теперь приложения могут устанавливаться с помощью Windows Installer, освобождает разработчиков от необходимости производить манипуляции с реестром, без которых установка приложений ранее была невозможной. Однако, несмотря на это, остается вероятность, что распространяемое приложение будет использовать системный реестр для сохранения информации о своей конфигурации.
Например, если приложение должно появляться в диалоговом окне Add/Remove Programs (Установка и удаление программ) панели управления, потребуется внести соответствующие записи в системный реестр. Системный реестр также может понадобиться для обеспечения обратной совместимости с унаследованным кодом.
Как и следовало ожидать от такой обширной библиотеки, как .NET, в ней поставляются классы, которые позволяют получать доступ к реестру. Таких классов два — Registry и RegistryKey, и оба они находятся в пространстве имен Microsoft.Win32. Прежде чем переходить к рассмотрению этих классов, давайте вкратце ознакомимся со структурой самого реестра.
Реестр имеет иерархическую структуру, во многом похожую на структуру файловой системы. Обычно для просмотра и изменения содержимого реестра применяются две утилиты: regedit и regedt32. Утилита regedit является стандартной и поставляется во всех версиях Windows, начиная с Windows 95. Утилита regedt32 входит в состав Windows NT и Windows 2000, и по сравнению с regedit менее дружественная к пользователю, но зато она позволяет получать доступ информации, связанной с безопасностью, которую regedit просматривать не позволяет.
В Windows Server 2003 эти утилиты были объединены в единый новый редактор, который называется просто regedit. В ходе обсуждений здесь имеется в виду утилита regedit, поставляемая в Windows 7, которую можно запустить, введя regedit в диалоговом окне для запуска программ либо в командной строке.
На рисунке показано, как выглядит окно regedit при запуске этой утилиты в первый раз. На этом рисунке видно, что regedit имеет пользовательский интерфейс в стиле "дерево/список", который похож на интерфейс проводника Windows и в точности отражает иерархическую структуру самого реестра. Однако вскоре будут показаны и некоторые важные отличия:
В файловой системе узлы верхнего уровня представляют разделы дисков — С:\ , D:\ и т.д. В реестре эквивалентом разделов дисков являются разделы реестра (registry hive). Изменять существующие разделы не допускается — они являются фиксированными и всего их семь, хотя в интерфейсе regedit видны только пять из них, которые перечислены ниже:
- HKEY_CLASSES_ROOT (HKCR)
содержит детали, касающиеся типов файлов в системе (.txt, .doc и т.д.), и о том, какие приложения способны открывать файлы каждого из этих типов. Также содержит регистрационную информацию обо всех компонентах СОМ (она обычно занимает больше всего места в реестре, поскольку в Windows в настоящее время поставляется огромное количество разнообразных СОМ-компонентов).
- HKEY_CURRENT_USER (HKCU)
содержит детали, касающиеся предпочтений пользователя, который в текущий момент локально работает в системе. К числу этих предпочтений относятся настройки рабочего стола, переменные окружения, подключение к сетям и принтерам и прочие параметры, которые определяют внешний вид рабочей среды пользователя.
- HKEY_LOCAL_MACHINE (HKLM)
представляет собой огромный раздел, в котором содержатся детали, касающиеся всего установленного на машине программного и аппаратного обеспечения. Также включает в себя раздел HKCU, который сам по себе не является отдельным разделом, а просто удобным отображением ключа реестра HKLM/SOFTWARE/Classes.
- HKEY_USERS (HKUSR)
содержит детали, касающиеся предпочтений всех пользователей. Как не трудно догадаться, он тоже содержит разделы HKCU, которые являются просто отображением соответствующих ключей HKEY_USERS.
- HKEY_CURRENT_CONFIG (HKCF)
содержит детали, касающиеся конфигурации установленного на машине оборудования.
В остальных двух корневых разделах размещается информация, которая является временной и часто изменяющейся:
- HKEY_DYN_DATA
является общим контейнером для любых текущих данных, которые требуется сохранять где-нибудь в реестре.
- HKEY_PERFORMANCE_DATA
содержит информацию, касающуюся производительности функционирующих в текущий момент приложений.
Внутри разделов находится древовидная структура ключей реестра. Каждый из них во многом напоминает папку или файл в файловой системе. Однако между ними имеется одно очень важное отличие. В файловой системе различаются файлы (в которых хранятся данные) и папки (в которых главным образом содержатся другие файлы или папки), а в реестре присутствуют только ключи. В каждом ключе могут содержаться как данные, так и другие ключи.
Если ключ содержит данные, то они представлены последовательностью значений. Каждое значение имеет ассоциированное с ним имя, тип данных и собственно данные. Вдобавок ключ имеет безымянное значение по умолчанию.
Всю эту структуру можно увидеть, используя утилиту regedit. На рисунке показано содержимое ключа HKCU\Console:
Записи в системном реестре могут иметь формат одного из трех типов данных:
REG_SZ (приблизительно соответствует экземпляру строки в .NET, но это сходство не точное, поскольку типы данных реестра не являются типами данных .NET);
REG_DWORD (приблизительно соответствует типу uint);
REG_BINARY (массив байт).
Приложение, предусматривающее сохранение каких-то данных в реестре, будет делать это за счет создания ряда ключей, причем обычно внутри ключа HKLM\Software\<Название компании>. Обратите внимание, что в этих ключах вовсе не обязательно должны содержаться какие-либо данные. Порой сам факт существования ключа позволяет приложению получать те данные, которые ему необходимы.
Как уже говорилось, доступ к реестру позволяют получать два класса из пространства имен Microsoft.Win32: Registry и RegistryKey. Экземпляр RegistryKey представляет ключ реестра. В этом классе есть методы для просмотра дочерних ключей, для создания новых ключей, а также для чтения и изменения значений в существующих ключах, т.е. можно выполнять все, что обычно требуется делать с ключами реестра, в том числе устанавливать для них уровни безопасности. Именно этот класс применяется для выполнения большей части работы с реестром.
В отличие от него, Registry представляет собой класс, который позволяет получать эксклюзивный доступ к ключам реестра для выполнения простых операций. Другим предназначением класса Registry является предоставление экземпляров RegistryKey, представляющих ключи наивысшего уровня, т.е. разделы, которые позволяют осуществлять навигацию по реестру. Предоставляются эти экземпляры через семь статических свойств со следующими именами: ClassesRoot, CurrentConfig, CurrentUser, DynData, LocalMachine, PerformanceData и Users. Каким разделам соответствуют эти свойства, должно быть вполне очевидно.
Например, для получения экземпляра RegistryKey, представляющего ключ HKLM, потребуется написать такой код:
RegistryKey hklm = Registry.LocalMachine;
Процесс получения ссылки на объект RegistryKey называется открытием ключа. Хотя можно было бы ожидать, что, поскольку реестр имеет такую же иерархическую структуру, что и файловая система, методы, предлагаемые классом RegistryKey, должны быть похожими на те, что реализованы в DirectoryInfo, на самом деле это не так. Часто способ доступа к реестру отличается от способа использования файлов и папок, и RegistryKey реализует методы, отражающие это.
Наиболее очевидное отличие связано с тем, как открывается ключ в определенном месте реестра. Класс Registry не имеет никакого общедоступного конструктора, который можно было бы использовать, равно как не имеет и методов, которые позволяли бы переходить к ключу по имени напрямую. Вместо этого добираться до нужного ключа придется от самого верхнего раздела.
Если необходимо создать экземпляр объекта RegistryKey, то единственно возможный способ предусматривает начать с соответствующего статического свойства класса Registry и оттуда уже двигаться вниз. То есть, например, для чтения данных из ключа HKLM\Software\Microsoft потребуется получить на него ссылку следующим образом:
RegistryKey hklm = Registry.LocalMachine;
RegistryKey hkSoftware = hklm.OpenSubKey("Software");
RegistryKey hkMicrosoft = hkSoftware.OpenSubKey("Microsoft");
Доступ к ключу подобным образом позволяет осуществлять только чтение. Если необходимо выполнить запись каких-то данных в ключ (в том числе запись его значения, а также создание или удаление его непосредственных дочерних элементов), потребуется использовать другую переопределенную версию OpenSubKey. Она принимает во втором параметре значение типа bool, указывающее, должен ли доступ к ключу предоставляться не только для чтения, но и для записи. Например, чтобы получить возможность изменить ключ Microsoft (имея права системного администратора), потребуется написать следующий код:
RegistryKey hklm = Registry.LocalMachine;
RegistryKey hkSoftware = hklm.OpenSubKey("Software");
RegistryKey hkMicrosoft = hkSoftware.OpenSubKey("Microsoft", true);
Кстати, поскольку в этом ключе содержится информация, используемая приложениями Microsoft, в большинстве случаев этот конкретный ключ модифицировать не стоит.
Метод OpenSubKey() вызывается в случаях, когда ожидается, что ключ уже существует. Если ключа там не оказывается, OpenSubKey() возвращает ссылку null. Чтобы создать ключ, необходимо использовать метод CreateSubKey() (который автоматически предоставляет доступ как для чтения и записи к ключу через возвращаемую ссылку):
RegistryKey hklm = Registry.LocalMachine;
RegistryKey hkSoftware = hklm.OpenSubKey("Software");
RegistryKey hkMine = hkSoftware.CreateSubKey("MyOwnSoftware");
Способ, которым работает метод CreateSubKey(), является довольно интересным. Он создает ключ, если его не существует, а если ключ существует, метод просто возвращает экземпляр RegistryKey, который представляет существующий ключ. Причина, по которой этот метод ведет себя подобным образом, связана с тем, как обычно используется реестр. В реестре в целом содержатся подлежащие длительному хранению данные наподобие конфигурационной информации для Windows и различных приложений. Поэтому необходимость создавать ключ явным образом возникает не очень часто.
Гораздо чаще приложениям требуется проверять наличие некоторой информации в реестре — другими словами, создавать ключи, если они еще не существуют, и ничего не делать, если ключи уже там есть. Метод CreateSubKey() просто идеально позволяет удовлетворить такую потребность. В отличие от ситуации с FileInfo.Open(), в случае применения CreateSubKey() случайное удаление каких-нибудь данных исключается. Если же на самом деле необходимо удалить какие-то ключи реестра, потребуется вызвать метод DeleteSubKey().
Учитывая важность реестра для Windows, в этом есть смысл. Вряд ли кому-то понравится перспектива случайно вывести систему Windows из строя, просто удалив несколько важных ключей во время отладки самостоятельно написанного кода C#, который работает с системным реестром.
После обнаружения подлежащего чтению или модификации ключа можно с помощью метода SetValue() или GetValue(), соответственно, установить либо извлечь данные из него. Оба эти метода принимают в качестве параметра строку с именем значения, но SetValue() также требуется дополнительно передать в качестве параметра ссылку на объект, который содержит детали, касающиеся значения. Поскольку по определению этот параметр должен представлять собой ссылку на объект, он на самом деле может быть ссылкой на любой класс. По типу предоставляемого класса метод SetValue() и будет определять тип для значения — REG_SZ, REG DWORD или REG_BINARY. Например:
RegistryKey hkMine = HkSoftware.CreateSubKey("MyOwnSoftware") ;
hkMine.SetValue("MyStringValue", "Hello World");
hkMine.SetValue("MyIntValue", 20);
В этом коде для ключа устанавливаются два значения: MyStringValue с типом REG_SZ и MyIntValue с типом REG_DWORD.