Статья
Версия для печати
Обсудить на форуме
Работа с реестром Windows.

Автор: Малая
Под редакцией Клуба программистов «Весельчак У».

Эта статья посвящена некоторым функциям, позволяющим нам работать с реестром. Сразу оговорюсь — к статье прикреплен класс CRegistryManip (см. раздел «Файлы»), в котором собраны некоторые примеры по применению вышеозначенных функций.

Содержание.


1. Краткое пояснение. Изначальная постановка задачи.

Была поставлена задача: создать пользовательский «MessageBox», который, кроме стандартных элементов типа «текст» и «кнопки», имел бы еще и «CheckBox», перевод которого в состояние «установлено» должен был бы приводить к тому, что это сообщение больше не будет показано.
Соответственно, эта информация должна быть сохранена в реестре. Причем в виде пары значений: «StringId» (все тексты должны быть внесены в «string table» из-за поддержки многоязычности в проекте) + «Flag» (т.е. состояние этого «CheckBox»).


Благодаря этому и появился вышеупомянутый класс для работы с реестром.

2. Общая информация.

2.1. Общий принцип построения реестра.

Реестр имеет иерархическую структуру, аналогичную каталогу (вложенные папки, деревья). Корневые элементы представляют собой установленные Майкрософтом константы.

Код:
HKEY_CLASSES_ROOT
HKEY_CURRENT_CONFIG
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS

Каждый из корневых элементов — корневых ключей — имеет подключи — папки (далее — ключи или подключи), те, в свою очередь, тоже могут иметь свои подключи и т.д. В подключе, находящемся в этой цепочке, могут содержаться параметры и соответствующие им значения. Например:

Код:
HKEY_LOCAL_MACHINE
    |
    SOFTWARE
        |
        Microsoft
            |
            IE4
                |
                Setup
                    |
                    Path = "%programfiles%\Internet Explorer"

В примере:
  • HKEY_LOCAL_MACHINE — корневой ключ.
  • Path — параметр;
  • все остальные — подключи.




2.2. Наши возможности.

С помощью стандартных функций мы можем:
  • считывать и запоминать имена ключей и параметров;
  • считывать и запоминать значения параметров;
  • создавать и удалять ключи;
  • получать общую информацию о ключах и их параметрах.

3. Описание функций.

3.1. Открываем и закрываем реестр.

Эти две команды - основа основ. И они самые простые в использовании. ;-)

Открываем реестр.

Код:
CRegistryManip::OpenRegKey (
    const HKEY hKey,
    const TCHAR* pSubKey,
    HKEY& hKeyPrograms,
    REGSAM samDesired = KEY_READ
);

Где:
  • hKey — один из корневых ключей в реестре:
    • HKEY_CLASSES_ROOT,
    • HKEY_CURRENT_CONFIG,
    • HKEY_CURRENT_USER,
    • HKEY_LOCAL_MACHINE,
    • HKEY_USERS;
  • pSubKey — подраздел в реестре, который нас интересует (например: «Microsoft\IE4\Setup»);
  • hKeyPrograms — указатель на handle (описатель) открытого ключа;
  • samDesired — уровень доступа (для чтения значений, для чтения имен параметров, записи, создания ключей и т.д.):
    • KEY_ALL_ACCESS — максимальный доступ. Представляет собой комбинацию ключей: KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK и KEY_SET_VALUE;
    • KEY_READ — для чтения информации из реестра. Представляет собой комбинацию ключей: KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS и KEY_NOTIFY;
    • KEY_WRITE — для записи информации в реестр. Представляет собой комбинацию ключей: KEY_SET_VALUE и KEY_CREATE_SUB_KEY;
    • KEY_QUERY_VALUE — для опроса значений параметров;
    • KEY_ENUMERATE_SUB_KEYS — для чтения имен подключей;
    • KEY_NOTIFY — для изменения нотификаций;
    • KEY_CREATE_SUB_KEY — для создания подключа;
    • KEY_CREATE_LINK — для создания линка;
    • KEY_SET_VALUE — для установки значений параметров.

Чтобы открыть ключ, используем стандартную функцию RegOpenKeyEx().

Краткое описание функции RegOpenKeyEx().

Эта функция получает: hKey — имя корневого ключа в реестре, pSubKey — имя подключа, который мы хотим открыть и samDesired — уровень доступа к информации. В случае удачного завершения функция возвращает hKeyPrograms — указатель на открытый ключ.
Возвращаемое значение функции ::RegOpenKeyEx() — тип ULONG. Значение ERROR_SUCCESS означает благополучное завершение выполнения функции, все остальное говорит о том, что нам где-то и в чем-то не повезло. ;-)


Вызов выглядит следующим образом:

Код:
CString cstrSubKey (_T("SOFTWARE\\Microsoft\\IE4\\Setup"));
HKEY hKeyPrograms = NULL;
ULONG nRes = OpenRegKey (
    HKEY_LOCAL_MACHINE,
    cstrSubKey,
    hKeyPrograms
);

Закрываем реестр.

(Какой-либо ключ, который был перед этим открыт с помощью OpenRegKey().)

Код:
CRegistryManip::CloseRegKey(
    HKEY hKey
);

Где hKey — тот самый указатель на handle (описатель) открытого ключа, который нам вернула функция OpenRegKey().

Чтобы закрыть ключ используем стандартную функцию RegCloseKey().

Краткое описание функции RegCloseKey().

Эта функция получает hKey — указатель на открытый ключ. Она закрывает наш сеанс работы с реестром, открытый функцией RegOpenKeyEx(). Эти функции всегда должны вызываться в паре.


Вызов выглядит следующим образом:

Код:
CString cstrSubKey (_T("SOFTWARE\\Microsoft\\IE4\\Setup"));
HKEY hKeyPrograms = NULL;
BOOL bRes = OpenRegKey (
    HKEY_LOCAL_MACHINE,
    cstrSubKey,
    hKeyPrograms
);

//.....

bRes = CloseRegKey (hKeyPrograms);

3.2. Проверяем наличие ключа в реестре.

Мы можем проверить, существует ли в реестре нужный нам ключ. Например, «HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IE4\Setup».

Код:
CRegistryManip::CheckIfKeyExists (
    const HKEY hKey,
    const TCHAR* pSubKey
);

Где:
  • hKey — один из корневых ключей в реестре:
    • HKEY_CLASSES_ROOT,
    • HKEY_CURRENT_CONFIG,
    • HKEY_CURRENT_USER,
    • HKEY_LOCAL_MACHINE,
    • HKEY_USERS;
  • pSubKey — подраздел в реестре, который нас интересует (в нашем случае: «SOFTWARE\Microsoft\IE4\Setup»).

Для проверки нам достаточно вызвать функцию OpenRegKey(): если интересующий нас ключ существует, функция вернет TRUE, иначе — FALSE.
Вызов выглядит следующим образом:

Код:
CString cstrSubKey (_T("SOFTWARE\\Microsoft\\IE4\\Setup"));
BOOL bRes = OpenRegKey (
    HKEY_LOCAL_MACHINE,
    cstrSubKey
);

3.3. Создаем новый ключ.

Например, «HKEY_LOCAL_MACHINE\SOFTWARE\Irina».

Код:
CRegistryManip::CreateNewKey (
    const HKEY hKeyBase,
    const TCHAR* pKeyPath,
    const TCHAR* pNewKey
);

Где:
  • hKeyBase — один из корневых ключей в реестре:
    • HKEY_CLASSES_ROOT,
    • HKEY_CURRENT_CONFIG,
    • HKEY_CURRENT_USER,
    • HKEY_LOCAL_MACHINE,
    • HKEY_USERS;
  • pKeyPath — подраздел в реестре, который нас интересует (в нашем случае: «SOFTWARE»). Этот ключ — родитель для нашего нового ключа;
  • pNewKey — наш новый ключ (в нашем случае — «Irina»).

Чтобы создать ключ, используем стандартную функцию RegCreateKeyEx().

Краткое описание функции RegCreateKeyEx().

Эта функция получает hKey (имя основного ключа в реестре), pSubKeyNew = pKeyPath (имя подключа, который мы хотим открыть) + pNewKey (имя нового подключа) и samDesired (уровень доступа к информации).
В случае удачного завершения функция возвращает hKeyPrograms (указатель на созданный подключ).

В списке есть еще некоторые параметры:
  • lpClass — имя класса (Я долго искала какую-либо информацию о том, каково практическое применение этого параметра, но ничего умного не нашла... Во всех приводимых примерах этот параметр равен NULL. Я последовала примеру. ;-) Если кто-то сможет меня поправить в этом вопросе, буду премного благодарна.);
  • dwOptions — специальный флаг, который по умолчанию равен REG_OPTION_NON_VOLATILE. В обычной жизни этого хватает (Особо осложненные жизненные обстоятельства я не рассматривала, так как у меня на это просто не было времени. Но я  думаю, что при необходимости всегда можно найти нужную информацию.);
  • lpSecurityAttributes — устанавливает правила для наследования (использования) полученного указателя на созданный подключ процессами — детьми основного процесса. Опять же, в обычной жизни может быть без зазрения совести равен NULL;
  • lpdwDisposition — флаг, который говорит о том, что либо этот ключ уже существует, либо еще нет и должен быть создан. Если установить его в NULL, то система сама решает, что ей делать исходя из наличия или отсутствия заданного ключа. То есть, если ключ уже существует, функция работает как RegOpenKeyEx(), если нет — она создает заданный ключ;
  • samDesired — уровень доступа к информации в реестре. Для создания ключа должен быть установлен как минимум равным KEY_CREATE_SUB_KEY (но лучше всего — KEY_ALL_ACCESS).


Еще одно замечание: никогда не получится создать параметр непосредственно под основным ключом (HKEY_CLASSES_ROOT и т.д.). Необходимо сначала создать свой подключ и только затем создавать параметры.

Вызов выглядит следующим образом:

Код:
CString cstrSubKey (_T("SOFTWARE"));
CString cstrNewKey (_T("Irina"));
BOOL bRes = CreateNewKey (
    HKEY_LOCAL_MACHINE,
    cstrSubKey,
    cstrNewKey
);

3.4. Удаляем подключ или параметр из реестра.

Код:
CRegistryManip::DeleteInfoFromRegistry (
    const HKEY hKey,
    const char* pSubKey,
    const TCHAR* pKeyName
);

Где:
  • hKey — один из корневых ключей в реестре: HKEY_CLASSES_ROOT  и т.д.;
  • pSubKey — подраздел в реестре, который нас интересует (например: «SOFTWARE\Irina»);
  • pKeyName — имя параметра (Например, подключ «Irina» иммет параметр «StringID» со значением, равным 2000. В этом случае pKeyName равно «StringID».).

Это основная функция, которая, в свою очередь, вызывает DeleteSubKeyFromRegistry() или DeleteParamFromRegistry() — в зависимости от того, что мы хотим удалить (описание функций — см. пункты «удаляем подключ» и «удаляем параметр»).
Вызов выглядит следующим образом:

Код:
BOOL bRes = FALSE;

// удаление параметра
bRes = DeleteInfoFromRegistry (
    HKEY_LOCAL_MACHINE,
    _T("SOFTWARE\\Irina"),
    _T("StringID")
);

// удаление подключа
bRes = DeleteInfoFromRegistry (
    HKEY_LOCAL_MACHINE,
    _T("SOFTWARE\\Irina"),
    NULL
);

Удаляем подключ.

Код:
CRegistryManip::DeleteSubKeyFromRegistry (
    const HKEY hKey,
    const char* pSubKey
);

Чтобы удалить подключ, используем стандартную функцию RegDeleteKey().

Краткое описание функции RegDeleteKey().

Эта функция получает:
  • hKey — указатель на открытый ключ;
  • pSubKey — имя подключа, который мы хотим удалить.


Удаляем параметр.

Код:
CRegistryManip::DeleteParamFromRegistry (
    const HKEY hKey,
    const char* pSubKey,
    const TCHAR* pKeyName
);

Если речь идет о подключе, то достаточно вызова стандартной функции RegDeleteKey().
Если же надо удалить параметр, то тогда надо сначала открыть реестр, затем удалить параметр при помощи стандартной функции RegDeleteValue(), в заключение закрыть реестр.
При открытии реестра уровень доступа должен быть как минимум KEY_SET_VALUE. Для удаления параметра используем стандартную функцию RegDeleteValue().

Краткое описание функции RegDeleteValue().

Эта функция получает:
  • hKey — указатель на открытый ключ;
  • lpValueName — имя параметра, который мы хотим удалить.


3.5. Устанавливаем значение параметра.

Например:

Код:
HKEY_LOCAL_MACHINE\\SOFTWARE\\Irina — подключ
    |
    StringId = 2000 (StringId — это имя параметра, 2000 — его значение)

Код:
CRegistryManip::SetOneValue (
    HKEY hKey,
    const TCHAR* pKeyName,
    const TCHAR* pKeyVal,
    DWORD dwType = REG_SZ
);

Где:
  • hKey — указатель на handle (описатель) открытого ключа, который нам возвращает функция OpenRegKey();
  • pKeyName — имя параметра;
  • pKeyVal — значение параметра;
  • dwType — тип параметра.

Чтобы установить параметр, используем стандартную функцию RegSetValueEx().

Краткое описание функции RegSetValueEx().

Эта функция получает:
  • hKey — указатель на открытый ключ;
  • lpReserved — зарезервированное значение, равно NULL;
  • pKeyName — имя параметра, значение которого мы хотим установить;
  • dwType — тип данных;
  • lpData — адрес переменной (т.е. области памяти), в которую  будет помещен результат (тип этой переменной зависит от типа данных);
  • cbData — размер области этой памяти.

Если тип данных — REG_SZ, то для получения результата надо создать массив типа char (или TCHAR, если мы работаем с Unicode).
Если тип данных — REG_BINARY, то для получения результата надо создать переменную типа int.
Если тип дfанных — REG_DWORD, то для получения результата надо создать переменную типа DWORD.

Применение преобразования любого из этих типов данных в (BYTE*) необходимо для получения правильного результата.


Вызов выглядит следующим образом:

Код:
CString cstrSubKey (_T("SOFTWARE\\Irina"));
CString cstrParName (_T("StringID"));
CString cstrParVal (_T("2000"));
HKEY hKeyPrograms = NULL;

// Сначала вызываем
// OpenRegKey(HKEY_LOCAL_MACHINE, cstrSubKey, hKeyPrograms),
// т.к. сначала надо открыть ключ, в котором находится
// наш параметр, затем вызываем функцию по установке
// значения параметра. В данном случае мы не передаем тип,
// т.к. он по умолчанию уже установлен как REG_SZ.

ULONG nRes = SetOneValue (
    hKeyPrograms,
    cstrParName,
    cstrParVal
);

// В конце закрываем реестр: CloseRegKey(hKeyPrograms).

3.6. Получаем значение параметра.

Код:
CRegistryManip::getInfoFromRegistry (
    const HKEY hKey,
    const TCHAR* pSubKey,
    const TCHAR* pRegKey,
    TCHAR *pRes,
    DWORD dwType = REG_SZ
);

Где:
  • hKey — один из корневых ключей в реестре: HKEY_CLASSES_ROOT и т.д.;
  • pSubKey — подраздел в реестре, который нас интересует (например, «Software\Irina»);
  • pKeyName — имя параметра (например: «StringID»);
  • pRes — значение параметра (возвращаемое этой функцией);
  • dwType — тип параметра.

Сама функция осуществляет следующие действия: открывает реестр и вызывает долонительную функцию CRegistryManip::GetOneValueFromReg(), которая считывает информацию из реестра в зависимости от типа данных:

Код:
CRegistryManip::GetOneValueFromReg (
    HKEY hKey,
    const TCHAR* pRegKey,
    TCHAR *pRes,
    DWORD dwType = REG_SZ
);

Где:
  • hKey — указатель на handle (описатель) открытого ключа, который нам вернула функция OpenRegKey();
  • pRegKey — имя параметра (например, «StringID»);
  • pRes — значение параметра, возвращаемое этой функцией;
  • dwType — тип параметра.

Для получения значения используем стандартную функцию RegQueryValueEx().

Краткое описание функции RegQueryValueEx().

Эта функция получает следующие параметры:
  • hKey — указатель на открытый ключ;
  • lpValueName — имя параметра, значение которого мы хотим получить;
  • lpReserved — зарезервированное значение, равно NULL;
  • lpType — адрес переменной (т.е. области памяти), в которую будет помещен тип результата;
  • lpData — адрес переменной, в которую будет помещен результат. В зависимости от типа данных контейнером для получения значения выступает либо строка символов, либо переменная типа int;
  • lpcbData — адрес переменной, в которой находится размер переменной, предназначенной для помещения в нее результата.

Применение преобразования любого из этих типов данных в (BYTE*) необходимо для получения правильного результата. Вызов выглядит следующим образом (я беру пример вызова этой функции из getInfoFromRegistry()):


Код:
HKEY hKeyPrograms; // получим при вызове OpenRegKey()
TCHAR dwBuffer[1024];
GetOneValueFromReg(hKeyPrograms, pRegKey, dwBuffer, dwType);

3.7. Получаем количество параметров.

Например:

Код:
HKEY_LOCAL_MACHINE\\SOFTWARE\\Irina - папка (подключ)
    |
    StringId    -> параметр
    |
    Flag        -> параметр
    |
    Param_bin   -> параметр

Итого в папке 3 параметра. Наша задача — получить это количество программным путем.

Код:
CRegistryManip::GetCountOfSubKeys (
    HKEY hKey,
    unsigned long& nSubKeysCount
);

Где:
  • hKey — указатель на handle (описатель) открытого ключа, который нам возвращает функция OpenRegKey();
  • nSubKeysCount — количество параметров, возвращаемое функцией значение.

Для получения этой информации используем стандартную функцию RegQueryInfoKey().

Краткое описание функции RegQueryInfoKey().

  • hKey — указатель на открытый ключ в реестре, который нас интересует;
  • lpClass — адрес переменной, где находится имя класса;
  • lpcbClass — адрес переменной, в которой находится размер буфера для имени класса;
  • lpReserved — зарезервированный параметр, равен NULL;
  • lpcSubKeys — адрес переменной, в которой находится результат — количество подключей;
  • lpcbMaxSubKeyLen — адрес переменной, в которой находится длина для имени подключа;
  • lpcbMaxClassLen — адрес переменной, в которой находится длина буфера, предназначенного для хранения имени класса;
  • lpcValues — адрес переменной, в которой находится еще один результат — количество параметров в данном ключе;
  • lpcbMaxValueNameLen — адрес переменной, в которой находится размер буфера для имени параметра или подключа;
  • lpcbMaxValueLen — адрес переменной, в которой находится размер буфера для значения параметра;
  • lpcbSecurityDescriptor — адрес переменной, в которой может находиться security-информация;
  • lpftLastWriteTime — адрес переменной типа FILETIME, в которой находится время последнего изменения этого ключа.


Комментарий: за исключением переменных hKey, lpcSubKeys, и lpcValues я
так и не поняла, где и как все остальное может быть использовано... Пардон ;-)

В нашем случае нас интересует значение переменной lpcValues (в нашем примере оно будет равно 3). Значение переменной lpcSubKeys в нашем примере будет равно 0, т.к. наш ключ не имеет вложенных подключей.

3.8. Получаем список всех параметров и их значений.

Код:
CRegistryManip::FindAllParamsWithValues (
    HKEY hKeyBase,
    const CString& csKeyPath,
    CStringArray& acsKeyList,
    CStringArray& acsValList
);

Где:
  • hKeyBase — один из корневых ключей в реестре: HKEY_CLASSES_ROOT и т.д.;
  • csKeyPath — подраздел в реестре, который нас интересует (например, «Software\Irina»);
  • acsKeyList — список имен параметров;
  • acsValList — список значений параметров.

Для получения этой информации нужно сделать несколько шагов:
  • с помощью функции GetCountOfSubKeys() получаем количество параметров;
  • для каждого параметра сначала считываем его имя с помощью функции RegEnumValue();
  • затем при наличии имени получаем всю информацию о параметре с помощью функции RegQueryMultipleValues(). Среди прочего мы получаем тип параметра. Это необходимо для получения значения параметра;
  • само значение мы получаем с помощью функции GetOneValueFromReg().

Краткое описание функции RegEnumValue().


  • hKey — указатель на открытый ключ, который нас интересует;
  • dwIndex — индекс параметра;
  • lpValueName — адрес буфера, в который будет помещено имя параметра;
  • lpcbValueName — адрес переменной, в которой находится размер буфера для имени параметра;
  • lpReserved — зарезервированный параметр, равен NULL;
  • lpType — адрес переменной, в которой находится тип возвращаемого значения. В нашем случае он равен REG_NONE, т.к. мы его не знаем и значение параметра с помощью этой функции не считываем;
  • lpData — адрес буфера, в который будет помещено значение параметра;
  • lpcbData — адрес переменной, в которой находится размер буфера для значения параметра.


Краткое описание функции RegQueryMultipleValues().


  • hKey — указатель на открытый ключ, который нас интересует;
  • val_list — указатель на структуру, в которой будут находиться результаты. Перед вызовом функции мы должны внести сюда имя параметра:

Код:
VALENT val_list[1];
val_list[0].ve_valuename = *lpValueName;
// получено перед этим с помощью RegEnumValue()

  • num_vals — размер структуры val_list;
  • lpValueBuf — адрес буфера, в который будет помещено значение параметра (если мы заранее не знаем тип значения, то возвращаемое значение может быть представлено в такой форме, в которой оно нечитабельно);
  • ldwTotsize — адрес переменной, в которой находится размер буфера для значения параметра.


3.9. Изменяем существующее значение.

У меня в проекте была пара значений «StringId» (основной) — «Flag» (зависимый от основного), которые должны были изменяться синхронно, посему в этой функции есть два имени параметров и, соответственно, два значения. В обычной жизни это может быть и один параметр.

Код:
BOOL CRegistryManip::ChangeOneVal (
    const HKEY hKey,
    const TCHAR* pSubKey,
    const TCHAR* pRegKeyMain,
    const TCHAR* pRegKeyToChange,
    int nValMain,
    int nValPaar,
    BOOL& bWasFound
);

Где:
  • hKey — один из корневых ключей в реестре: HKEY_CLASSES_ROOT и т.д.;
  • pSubKey — имя подключа;
  • pRegKeyMain — имя основного параметра (при изменениях этот параметр остается нетронутым);
  • pRegKeyToChange — имя вспомогательного параметра, который и надо изменить;
  • nValMain — значение основного параметра;
  • nValPaar — новое значение вспомогательного параметра;
  • bWasFound — указывает на успешное завершение.

Для изменения значения параметра нужно сделать несколько шагов:

  • сначала с помощью CheckIfKeyExists() проверяем, существует ли заданный подключ;
  • если нет — сначала создаем этот подключ. Для этого:
    • анализируем строку pSubKey, разбивая ее на подэлементы на основании разделителя "\\";
    • и затем проверяем каждый из них на наличие в registry с помощью CheckIfKeyExists();
    • если папка не существует, создаем ее с помощью CreateNewKey().
    Пример: pSubKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irina\\projects\\proj_1\\settings".
    Подэлементы: HKEY_LOCAL_MACHINE, SOFTWARE, Irina, projects, proj_1, settings.
    Проверяем по нарастающей:
    • HKEY_LOCAL_MACHINE\\SOFTWARE,
    • HKEY_LOCAL_MACHINE\\SOFTWARE\\Irina,
    • HKEY_LOCAL_MACHINE\\SOFTWARE\\Irina\\projects и т.д.
  • затем находим в заданном подключе все параметры и их значения с помощью FindAllParamsWithValues();
  • полученную информацию анализируем:
    • если уже есть параметр pRegKeyMain со значением nValMain, то мы просто меняем имя вспомогательного параметра — SetOneValue();
    • если параметр pRegKeyMain со значением nValMain отсутствует, сначала создаем его, а затем создаем дополнительный параметр — InputNewValue().

На этом позволю себе закруглиться. Если кому-то это описание поможет, буду очень рада.

С уважением, Малая.

4. Файлы.

Версия для печати
Обсудить на форуме