В чём разница между HKEY и HANDLE?

В комментариях к посту про обертку для HANDLE зашла речь о разнице между HKEY и HANDLE. С одной стороны, они имеют много общего:

С другой стороны, есть и отличия:

В чём тут дело? Дело в том, что только часть функциональности реестра реализована в ядре. Она включает в себя базовые операции (создание, удаление, чтение, запись и т.д.) для работы с локальными ключами реестра. Остальные функции реализуются библиотекой advapi32.dll и работают в пользовательском режиме:

“Ядерная” часть функциональности доступна через функции Native API: NtCreateKey, NtOpenKey и т.д. При сравнении этих функций с функциями Win32 API видно, что Native API использует “классические” описатели HANDLE вместо HKEY:

NTSYSAPI
NTSTATUS
NTAPI
NtCreateKey(
    OUT PHANDLE             pKeyHandle,
    IN ACCESS_MASK          DesiredAccess,
    IN POBJECT_ATTRIBUTES   ObjectAttributes,
    IN ULONG                TitleIndex,
    IN PUNICODE_STRING      Class OPTIONAL,
    IN ULONG                CreateOptions,
    OUT PULONG              Disposition OPTIONAL);

Второе, не столь явное отличие состоит в том, что на уровне Native API реестр выглядит совсем по-другому. Вместо нескольких корневых псевдоключей HKEY_XXX используется единственный ключ “\REGISTRY” с двумя подключами “\USER” и “\MACHINE”:

\REGISTRY
    \USER
        \.DEFAULT
        \S-FOO
        \S-BAR
    \MACHINE
        \HARDWARE
        \SAM
        \SECURITY
        \SOFTWARE
        \SYSTEM

Функции RegCreateKey(Ex) и RegOpenKey(Ex) отображают пути как показано в следующей таблице:

См. Predefined Keys.

В случае доступа к одной из вервей реестра из этой таблицы различия между Win32 и Native API функциями заканчиваются. Win32 функции просто преобразовывают полученный HANDLE в HKEY перед тем, как вернуть описатель приложению. Именно для этих описателей работают функции DuplicateHandle и GetHandleInformation.

При обращении к реестру на удаленной машине Win32 API функции выполняют удалённый вызов по RPC протоколу. Значение описателя, возвращаемое в этом случае, не является значением типа HANDLE на локальной машине и вызов (Nt)CloseHandle для этого описателя вернет ошибку. Чтобы различать два типа описателей RegXxx функции устанавливают младший бит “удалённого” описателя в единицу. Это возможно, поскольку два младших бита обычно не используются. Соответственно, когда RegCloseKey получает такой описатель, вместо вызова NtCloseHandle освобождается удаленный описатель.

Мне, кстати, пока не удалось разобраться, как именно работает этот механизм, так что я могу сильно ошибаться.

Аналогично, в случае доступа к ветке HKEY_PERFORMANCE_DATA вместо обращения к реестру выполняется вызов одной функции из perflib.

comments powered by Disqus