RegOpenKeyExでレジストリを安全に読み書きする手順
Windowsのレジストリは、アプリケーション設定、ユーザー環境、ファイル関連付けなどを保存する階層型データベースです。Win32 APIから操作する場合は、対象キーを開く、値の型とサイズを確認する、必要に応じて値を書き込む、最後にハンドルを閉じるという流れになります。
中心となる関数がRegOpenKeyExです。この関数は既存のレジストリキーを開き、後続のRegQueryValueExやRegSetValueExで値を読み書きするためのHKEYハンドルを返します。キーが存在しない場合、RegOpenKeyExだけで新規作成することはできません。
対象が32ビットか64ビットか、管理者権限が必要か、文字列かDWORDかといった条件によって実装上の注意点が変わります。次の整理を先に確認しておくと、APIの選択を誤りにくくなります。
| 目的 | 主な関数 | ポイント |
|---|---|---|
| 既存キーを開く | RegOpenKeyEx | 読み取り・書き込み権限を指定する |
| キーを作成または開く | RegCreateKeyEx | 存在しないキーにも対応する |
| 値を読み取る | RegQueryValueEx | 型とバッファサイズを確認する |
| 値を書き込む | RegSetValueEx | REG_SZやREG_DWORDなどの型を指定する |
| キーを閉じる | RegCloseKey | 使い終わったHKEYを必ず解放する |
レジストリキーを開く基本構文
RegOpenKeyExの代表的な宣言は次のとおりです。Unicode版を明示的に使う場合はRegOpenKeyExW、ANSI版を使う場合はRegOpenKeyExAを呼び出します。一般的なWindowsアプリケーションでは、文字化けを避けやすいUnicode版が適しています。
HKEY hKey = nullptr;
LONG result = RegOpenKeyExW(
HKEY_CURRENT_USER,
L"Software\\SampleApp",
0,
KEY_READ,
&hKey
);
if (result == ERROR_SUCCESS) {
// hKeyを使って値を読み取る
RegCloseKey(hKey);
}
第1引数にはHKEY_CURRENT_USERやHKEY_LOCAL_MACHINEなどのルートキーを指定します。第2引数はルートからの相対パスで、区切り文字にはバックスラッシュを使います。第4引数のKEY_READは読み取り、第5引数は取得したハンドルの格納先です。APIの戻り値はGetLastErrorではなく、ERROR_SUCCESSやERROR_FILE_NOT_FOUNDなどのエラーコードとして扱います。
読み取り権限とルートキーの選択
ユーザーごとの設定を保存するなら、通常はHKEY_CURRENT_USER配下を選びます。この場所なら管理者権限なしで書き込めるケースが多く、複数ユーザー環境でも設定が混在しません。PC全体に適用する設定はHKEY_LOCAL_MACHINEが候補ですが、書き込み時に管理者権限や適切なアクセス制御が必要になります。
読み取りだけならKEY_READ、値の変更まで行うならKEY_READ | KEY_WRITEを指定します。必要以上にKEY_ALL_ACCESSを要求すると、権限不足やセキュリティ監査上の問題につながります。レジストリの場所を設計する際は、Windows APIの資料を整理したWindows APIリファレンスも関連関数を探す手掛かりになります。
LONG result = RegOpenKeyExW(
HKEY_CURRENT_USER,
L"Software\\SampleApp",
0,
KEY_READ | KEY_WRITE,
&hKey
);
RegQueryValueExで値を読み取る
値の読み取りでは、最初に必要なバッファサイズを調べ、次に実データを取得する方法が安全です。REG_SZの場合、サイズはバイト単位で返されます。終端のNULL文字を含めるかどうかは値の内容やAPIの使い方に依存するため、確保したバッファを初期化しておくと扱いやすくなります。
DWORD type = 0;
DWORD size = 0;
LONG result = RegQueryValueExW(
hKey, L"InstallPath", nullptr, &type, nullptr, &size
);
if (result == ERROR_SUCCESS && type == REG_SZ) {
std::vector<wchar_t> buffer(size / sizeof(wchar_t) + 1, L'\0');
result = RegQueryValueExW(
hKey, L"InstallPath", nullptr, &type,
reinterpret_cast<BYTE*>(buffer.data()), &size
);
}
REG_DWORDを読む場合はDWORD型の変数を用意し、そのサイズを渡します。REG_EXPAND_SZは環境変数を含む文字列なので、取得後にExpandEnvironmentStringsWを使うことがあります。存在しない値はERROR_FILE_NOT_FOUND、型が想定と違う場合はERROR_DATATYPE_MISMATCHとして処理を分けると、設定破損を発見しやすくなります。
RegSetValueExで値を書き込む
値を書き込むには、RegOpenKeyExでKEY_SET_VALUEまたはKEY_WRITEの権限を取得したハンドルを使います。文字列ならREG_SZ、32ビット整数ならREG_DWORDを指定します。文字列のサイズは終端NULLを含むバイト数にするのが基本です。
const wchar_t* path = L"C:\\Program Files\\SampleApp";
DWORD bytes = static_cast<DWORD>(
(wcslen(path) + 1) * sizeof(wchar_t)
);
LONG result = RegSetValueExW(
hKey,
L"InstallPath",
0,
REG_SZ,
reinterpret_cast<const BYTE*>(path),
bytes
);
書き込み先のキーが存在しない可能性があるなら、RegOpenKeyExではなくRegCreateKeyExを使います。既存キーを開いた場合と新規作成した場合で処理を分ける必要はありませんが、ERROR_ACCESS_DENIEDが返ったときは権限だけでなく、セキュリティソフトやグループポリシーの制限も確認します。設定ファイルで代替できる情報を無理にレジストリへ保存しない判断も重要です。
32ビットと64ビットの違い
64ビットWindowsでは、32ビットアプリケーションがHKEY_LOCAL_MACHINE\Softwareなどへアクセスすると、レジストリリダイレクトによって別のビューへ案内されることがあります。32ビット用と64ビット用のアプリケーションが同じ設定を共有する想定なら、実際にどのビューへ保存されているかを確認しなければなりません。
RegOpenKeyExのsamDesired引数にKEY_WOW64_32KEYまたはKEY_WOW64_64KEYを組み合わせると、アクセスするビューを指定できます。ただし、対象アプリケーションの設計を無視して固定すると、別プロセスから設定が見えなくなる場合があります。テスト環境ではregeditだけでなく、実際のビルド構成から値を確認してください。
権限昇格を目的にHKEY_LOCAL_MACHINEへ書き込む設計は避け、ユーザー設定にはHKEY_CURRENT_USERを優先します。プロセス起動など別のWin32処理とレジストリ設定を連携させる場合は、CreateProcessの解説も合わせて確認すると、ハンドルやエラー処理の考え方を統一できます。
エラー処理と安全な後片付け
レジストリAPIは、失敗しても必ずしも例外を発生させません。各関数の戻り値を保存し、ERROR_SUCCESSとの比較、ERROR_FILE_NOT_FOUNDやERROR_ACCESS_DENIEDへの対応、想定外のコードの記録を行います。ログにはキーのパスや値の内容をそのまま出力せず、パスワードやトークンなどの秘密情報を保護してください。
ハンドルを取得したら、成功時だけでなく途中で読み取りに失敗した場合もRegCloseKeyを呼び出します。C++ではスコープ終了時に自動でRegCloseKeyを呼ぶラッパーを用意すると、早期returnによる解放漏れを防げます。読み書きが終わった後に別処理へ移る前に、開いたHKEYを確実に閉じることが基本です。
まずはHKEY_CURRENT_USER\Software配下にテスト用キーを作り、RegOpenKeyExW、RegQueryValueExW、RegSetValueExW、RegCloseKeyの戻り値をログへ記録する小さな検証プログラムを作成してください。