May 2017

Volume 32 Number 5

C++ - 最新の C++ を使用した Windows レジストリへのアクセス

Giovanni Dicanio

Windows OS は、開発者がレジストリにアクセスできるように、一連の C インターフェイス API を公開しています。このような API の一部は非常に低レベルで、プログラマは細部詳細の多くに注意を払う必要があります。Windows Vista からは、これに混在するように、ある種高度な API として RegGetValue 関数 (bit.ly/2jXtfpJ) が加わりました。この API が導入されるまでは、レジストリから値を読み取るとき、RegOpenKeyEx を呼び出して、読み取る値を含む、目的のレジストリ キーを最初に開く必要がありました。次に、RegQueryValueEx API を呼び出して、多くの複雑な詳細に対処しなければなりません。たとえば、RegQueryValueEx を使用して文字列値を読み取ると、返される文字列は正しく NUL 終端されていることが保証されないため、コード内でセキュリティに関する一連の深刻なバグの原因になる可能性があります。バグを生み出さないようにするには、返される文字列に NUL 終端子があるかどうかをチェックし、ない場合は追加しなければなりません。さらに、RegCloseKey を呼び出して、開いたキーを正しく確実に閉じる必要があります。

当然、レジストリ キーを開けない場合があるため、それに対処するコードも同様に追加する必要があります。RegGetValue API はこのワークフローを簡略化します。目的のレジストリ キーを自動的に開き、使用後にはそのキーを閉じ、文字列を適切に NUL 終端してから呼び出し元に返します。このように簡略になっていても、RegGetValue 関数が低レベルの C インターフェイス関数であることは変わりません。その上、さまざまな型のレジストリ値 (DWORD、文字列、バイナリ データなど) を処理できることにより、インターフェイスのプログラミングが複雑になっています。

さいわい、最新の C++ を使用すれば、RegGetValue Win32 API をラップする高水準の抽象化を適切に構築し、さまざまな型の値をレジストリから読み取るためシンプルなインターフェイスを提供できます。

例外を使用したエラーの表現

RegGetValue API は C インターフェイスの API です。そのため、リターン コードを使用して呼び出し元にエラー状態を通知します。具体的には、この関数は LONG 型の値を返します。 正常終了した場合は ERROR_SUCCESS (ゼロ) になり、エラーが発生した場合は異なる値になります。たとえば、呼び出し元から提供された出力バッファーのサイズが、API がデータを書き込むのに足りなければ、関数は ERROR_MORE_DATA を返します。この C API をラップする高度な C++ インターフェイスを構築する場合、C++ 例外クラスを定義してエラーを表現できます。このクラスを標準 std::runtime_error クラスから派生し、RegGetValue が返す LONG 型のエラー コードを内部に埋め込むことができます。

class RegistryError
  : public std::runtime_error
{
public:
  ...
private:
  LONG m_errorCode;
};

また、HKEY やサブ キーの名前など、他の情報を例外オブジェクトに埋め込むことも可能です。これは基本実装にすぎません。

コンストラクターを追加して、この例外クラスのインスタンスを作成し、失敗した RegGetValue 呼び出しからのエラー メッセージとリターン コードを使用してもかまいません。

RegistryError(const char* message, LONG errorCode)
  : std::runtime_error{message}
  , m_errorCode{errorCode}
{}

エラー コードは、読み取り専用のアクセサー (ゲッター) を使用してクライアントに公開できます。

LONG ErrorCode() const noexcept
{
  return m_errorCode;
}

これで例外クラスを構築したので、使いやすくバグの少ない高度な C++ インターフェイスに RegGetValue C API をラップできるようになります。

レジストリからの DWORD 値の読み取り

では、RegGetValue API を使用してレジストリから DWORD 値を読み取る簡単な操作から始めましょう。この場合の使用パターンは非常にシンプルです。しかし、まず、この状況を管理するために、C++ で定義できるインターフェイスの種類を確認しておきます。

以下は、RegGetValue API のプロトタイプです。

LONG WINAPI RegGetValue(
  _In_        HKEY    hkey,
  _In_opt_    LPCTSTR lpSubKey,
  _In_opt_    LPCTSTR lpValue,
  _In_opt_    DWORD   dwFlags,
  _Out_opt_   LPDWORD pdwType,
  _Out_opt_   PVOID   pvData,
  _Inout_opt_ LPDWORD pcbData
);

ご覧のように、この C インターフェイス関数は、void* 出力バッファー (pvData) や入出力バッファーのサイズ パラメーターなど、極めて一般的なデータを受け取ります。また、レジストリ キーやそのキーの具体的な値名を特定する C スタイルの文字列 (lpSubKey と lpValue) もあります。この C 関数のプロトタイプは、C++ の呼び出し元に対してシンプルにするために多少微調整できます。

まず、C++ の例外をスローしてエラー状態を通知するように、C++ ラッパーはレジストリから読み取る DWORD 値を戻り値として返すだけにします。この調整により、void* 出力バッファー パラメーター (pvData) そのものと、関連するサイズ パラメーター (pcbData) が自動的に必要なくなります。

さらに、C++ を使用しているため、C スタイルのロウ ポインターの代わりに std::wstring クラスを使用して、Unicode (UTF-16) 文字列を表現するのが適切です。つまり、DWORD 値をレジストリから読み取るために、このように非常に単純化した C++ 関数は以下のように定義できます。

DWORD RegGetDword(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

ご覧のように PVOID パラメーターと LPDWORD パラメーターはありません。入力文字列は、const 参照を使って std::wstring オブジェクトに渡します。レジストリから読み取った値は、この C++ 関数が DWORD 値として返します。これで間違いなく、大幅に簡略化した高度なインターフェイスになります。

ここからは、その実装について詳しく見ていきます。前述のように、この場合の RegGetValue の呼び出しパターンは非常にシンプルです。レジストリから読み取った値を格納する DWORD 変数を宣言する必要があるだけです。

DWORD data{};

次に、RegGetValue が書き込む出力バッファーのサイズ (バイト単位) を示す DWORD 変数がもう 1 つ必要です。このシンプルな例の出力バッファーは、最初に宣言した "data" 変数にすぎないので、サイズは一定で、DWORD のサイズになります。

DWORD dataSize = sizeof(data);

ただし、dataSize は RegGetValue の入力パラメーターと出力パラメーターの両方になるため、"const" とマークを付けることはできません。

これで、RegGetValue API を以下のように呼び出すことができます。

LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_DWORD,
  nullptr,
  &data,
  &dataSize
);

入力の wstring オブジェクトは、wstring::c_str メソッドを使って C スタイル文字列のロウ ポインターに変換されます。RRF_RT_REG_DWORD フラグは、レジストリ値の型を DWORD に限定します。異なる型のレジストリ値の読み取りを試みると、RegGetValue 関数呼び出しは失敗しますが問題は起こりません。

最後の 2 つのパラメーターは、出力バッファーのアドレス (この場合は data 変数のアドレス) と、出力バッファーのサイズを格納する変数のアドレスを表します。実際には、関数から戻る際に、RegGetValue は出力バッファーに書き込まれた data のサイズを報告します。単なる DWORD を読み取る今回の例では、データのサイズは常に 4 バイト、sizeof(DWORD) です。ただし、このサイズ パラメーターは、文字列などの可変サイズの値の場合は重要性が高くなります。これについては、後ほど取り上げます。

RegGetValue 関数を呼び出した後は、リターン コードをチェックして、エラーの場合は以下のように例外をスローします。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read DWORD from registry.", retCode};
}

RegGetValue から返されるエラー コード (retCode) は例外オブジェクトに埋め込まれているため、後からコードでこのオブジェクトを取得して、この例外をさらに処理できます。

関数が正常終了した場合は、DWORD 型の data 変数が呼び出し元に返されるだけです。

return data;

これで、関数の実装は完了です。

呼び出し元は、この C++ ラッパー関数を、以下のようなコードで呼び出すだけです。

DWORD data = RegGetDword(HKEY_CURRENT_USER, subkey, L"MyDwordValue");

オリジナルの RegGetValue C API 呼び出しと比べると、このコードは非常にシンプルです。開いたレジストリ キー (この例では、事前に定義されている HKEY_CURRENT_USER キー) へのハンドル、サブ キーを含む文字列、および値の名前を渡すだけです。正常終了すると、DWORD 値が呼び出し元に返されます。エラーが発生すると、RegistryError 型のカスタム例外がスローされます。この種のコードは、RegGetValue の呼び出しよりも高度かつ単純です。実際には、RegGetValue の複雑さが、カスタム RegGetDword C++ ラッパー関数内に隠ぺいされています。

同じパターンを使用して、QWORD (64 ビット データ) 値をレジストリから読み取ることができます。今回の場合は、レジストリ値の DWORD 型を、64 ビットの ULONGLONG 型に置き換えるだけです。

レジストリからの文字列値の読み取り

レジストリから DWORD 値を読み取るのは非常に簡単です。RegGetValue Win32 API を 1 回呼び出すだけです。これは、DWORD 値が固定サイズの 4 バイト (DWORD のサイズ) であるためです。これに対して、文字列をレジストリから読み取る場合は、文字列が可変サイズ データになることから、もう少し複雑になります。このような場合は、RegGetValue API を 2 回呼び出すことになります。 最初の呼び出しでは、この API に出力文字列バッファーに必要なサイズを返すよう求めます。次に、正しいサイズのバッファーを動的に割り当てます。最後に、RegGetValue への 2 回目の呼び出しを行い、文字列データを割り当てたバッファーに実際に書き込みます。このパターンについては、以前のコラム「Win32 API の境界で STL 文字列を使用する」(msdn.com/magazine/mt238407) を参照してください。

まず、C++ の高度なラッパー関数のプロトタイプを見ておきます。

std::wstring RegGetString(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

DWORD の場合と同様、オリジナルの複雑な RegGetValue C API のプロトタイプと比べて、大幅にシンプルになっています。文字列値は、関数から std::wstring インスタンスとして返されます。エラーの場合は、例外がスローされます。サブキーの名前と値の名前はどちらも、入力 wstring の const 参照パラメーターとして渡します。

ここからは、実装コードについて説明します。

先ほど説明したように、RegGetValue API の最初の呼び出しで文字列値を格納する出力バッファーのサイズを取得します。

DWORD dataSize{};
LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_SZ,
  nullptr,
  nullptr,
  &dataSize
);

呼び出し構文は前の DWORD 値の場合と似ています。wstring オブジェクトは、wstring::c_str メソッドを呼び出して、C スタイルの文字列ポインターに変換されます。RRF_RT_REG_SZ フラグは、レジストリの有効な型を文字列型 (REG_SZ) に限定します。正常終了すると、RegGetValue API は目的の出力バッファーのサイズ (バイト単位) を変数 dataSize に書き込みます。

エラーが発生した場合は、以下のようにカスタム RegistryError クラスの例外をスローする必要があります。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read string from registry", retCode};
}

目的の出力バッファーのサイズを把握したら、出力文字列に必要なサイズで wstring オブジェクトを割り当てます。

std::wstring data;
data.resize(dataSize / sizeof(wchar_t));

RegGetValue が返す dataSize の値はバイト単位で表現されますが、wstring::resize が想定するサイズ表現は wchar_t count です。そこで、バイト単位のサイズ値を sizeof(wchar_t) で除算して、バイト単位から wchar_t に変換します。

これで、文字列を格納できるだけの領域を確保したので、この内部バッファーへのポインターを RegGetValue API に渡せるようになります。この RegGetValue API は、実際の文字列データを、指定のバッファーに書き込みます。

retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_SZ,
  nullptr,
  &data[0],
  &dataSize
);

&data[0] は、RegGetValue API が書き込む wstring 内部バッファーのアドレスです。

いつものように、API の呼び出し結果を検証して、エラーの場合は例外をスローすることが重要です。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read string from registry", retCode};
}

正常終了した場合、RegGetValue は実際の文字列サイズ (バイト単位) を変数 dataSize に書き込みます。このサイズに応じて、wstring オブジェクトのサイズを変更します。dataSize はバイト単位で表現されるため、wstring を扱う場合は、これに対応する wchar_t に変換します。

DWORD stringLengthInWchars = dataSize / sizeof(wchar_t);

さらに、dataSize は、出力文字列の NUL 終端文字もサイズに含みます。ただし、wstring オブジェクトは既に NUL 終端されているため、読み取り文字列に NUL 終端子を加えて 2 重終端にならないように注意してください。今回は、RegGetValue が書き込む NUL 終端子を取り除きます。

stringLengthInWchars--; // Exclude the NUL written by the Win32 API
data.resize(stringLengthInWchars);

RegGetValue API は、レジストリに格納されている元の文字列が NUL 終端されていない場合でも、正常終了時に必ず NUL 終端された文字列になるようにします。この動作は、返される文字列が NUL 終端されていることが保証されない以前の RegQueryValueEx API よりもはるかに安全です。以前の API では、呼び出し元が、NUL 終端されていることを適切に判断するコードを追加で作成する必要があるため、コード全体の複雑さが増し、バグが起こる可能性が広がっていました。

今回の場合は、data 変数にレジストリから読み取った文字列値を含むため、関数の終了時に呼び出し元に返すことができます。

return data;

低レベルの C API RegGetValue をラップする RegGetString C++ ラッパーを用意したら、以下のように呼び出すことができます。

wstring s = RegGetString(HKEY_CURRENT_USER, subkey, L"MyStringValue");

DWORD の場合と同様、RegGetValue Win 32 API からの抽象化のレベルを上げ、レジストリから文字列値を読み取る、使いやすく誤用の少ない C++ ラッパー関数を提供しました。RegGetValue API を操作する際の詳細や複雑さはすべて、このカスタム RegGetString C++ 関数の本体内部に問題なく隠ぺいされます。

レジストリからの複数行文字列値の読み取り

レジストリ値の型には、いわゆる「複数行文字列」もあります。 基本的には、これは一連の文字列が2 重 NUL 終端され、1 つのレジストリ値にパックされたものです。このような 2 重終端される文字列のデータ構造は、メモリ内で隣接する、一連の NUL 終端された C スタイル文字列で構成されます。一連の文字列の最後は、さらに NUL 終端子でマークされるため、構造全体は 2 つの NUL で終端されることになります。このデータ構造の詳細については、bit.ly/2jCqg2u のブログ記事「What Is the Format of a Double-Null-Terminated String with No Strings?」(文字列ではない 2 重 NUL 終端される文字列の形式とは) を参照してください。

この場合の RegGetValue Win32 API の使用パターンは、先ほどの単一文字列の場合とほぼ同じです。つまり、最初に RegGetValue API を呼び出して、目的のデータ (この場合、2 つの NUL で終端される隣接した文字列全体) を含む目標のバッファー全体のサイズを取得します。次に、そのサイズのバッファーを動的に割り当てます。最後に、割り当てたバッファーのアドレスを渡して RegGetValue 関数をもう 1 回呼び出し、API が実際の複数行文字列データをバッファーに書き込めるようにします。

この場合、2 つの NUL で終端される文字列を格納するデータ構造に注意する必要があります。実際、std::wstring は正しく NUL を埋め込みますが、その結果 2 重 NUL 終端される文字列構造で格納される可能性があります。ただし、今回は抽象化のレベルを上げ、2 重 NUL 終端された文字列を高度で便利な vector<wstring> に変換します。

したがって、複数行文字列値をレジストリから読み取る C++ ラッパー関数のプロトタイプは、以下のようになります。

std::vector<std::wstring> RegGetMultiString(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

正常終了すると、複数行文字列が優れた vector<wstring> として、呼び出し元に返されます。エラーが発生した場合、通常の RegistryError の形式で例外がスローされます。

C++ ラッパー関数の本体内部では、最初に RegGetValue API を呼び出して、複数行文字列を格納する出力バッファーのサイズを取得します。

DWORD dataSize{};
LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_MULTI_SZ,
  nullptr,
  nullptr,
  &dataSize
);

今回は RRF_RT_REG_MULTI_SZ フラグを使用しています。このフラグは、複数行文字列レジストリ値型を指定します。

いつものように、エラーの場合は例外をスローし、RegistryError オブジェクトにエラー コードを埋め込みます。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read multi-string from registry", retCode};
}

正常終了すると、複数行文字列全体を格納する、適切なサイズのバッファーが確保されます。

std::vector<wchar_t> data;
data.resize(dataSize / sizeof(wchar_t));

複数行文字列のバッファーをそのまま表現するには、wstring よりも vector<wchar_t> の方が明確です。さらに、RegGetValue API から返されるサイズ値はバイト単位で表現されるため、値を wchar_t count に正しく変換してから vector::resize メソッドに渡す必要があります。

その後、RegGetValue API をもう 1 回呼び出し、実際の複数行文字列データを、先ほど割り当てたバッファーに書き込みます。

retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_MULTI_SZ,
  nullptr,
  &data[0],
  &dataSize
);

引数 &data[0] は出力バッファーの先頭を指します。

繰り返しになりますが、この API のリターン コードをチェックし、エラーを通知する場合は C++ 例外をスローします。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read multi-string"
    from registry", retCode};
}

RegGetValue API から出力パラメーターとして返される dataSize の値を使って、データ バッファーのサイズを正しく変更します。

data.resize( dataSize / sizeof(wchar_t) );

この時点で、data 変数 (vector<wchar_t>) には、2 重 NUL 終端される文字列のシーケンスが格納されます。最後の手順として、このデータ構造を解析して、より高度で便利な vector<wstring> に変換します。

// Parse the double-NUL-terminated string into a vector<wstring>
std::vector<std::wstring> result;
const wchar_t* currStringPtr = &data[0];
while (*currStringPtr != L'\0')
{
  // Current string is NUL-terminated, so get its length with wcslen
  const size_t currStringLength = wcslen(currStringPtr);
  // Add current string to result vector
  result.push_back(std::wstring{ currStringPtr, currStringLength });
  // Move to the next string
  currStringPtr += currStringLength + 1;
}

これで、結果の vector<wstring> オブジェクトを呼び出し元に返すことができます。

return result;

この RegGetMultiString C++ ラッパーの呼び出しは、以下のように簡単です。

vector<wstring> multiString = RegGetMultiString(
  HKEY_CURRENT_USER,
  subkey,
  L"MyMultiSz"
);

繰り返しになりますが、Win32 RegGetValue API の複雑さはすべて、高度で便利な C++ インターフェイスに隠ぺいされています。

レジストリ キーの値の列挙

Windows レジストリで一般的なもう 1 つの操作は、特定のレジストリ キーの値の列挙です。Windows はこの目的に RegEnumValue API (bit.ly/2jB4kaV) を用意しています。ここでは、この API を使用して特定のレジストリ キーの値の名前と型のリストを取得し、その列挙プロセスを便利で高度な C++ 関数にラップする方法を見ていきます。このカスタム C++ 関数は、列挙するキーに関連付けられた有効な HKEY ハンドルを入力として受け取ります。正常終了すると、このカスタム C++ ラッパー関数は、ベクトルのペアを返します。 ペアの最初の項目は値の名前を含む wstring、2 番目の項目は値の型を表す DWORD です。したがって、この C++ ラッパー関数のプロトタイプは以下のようになります。

std::vector<std::pair<std::wstring, DWORD>> RegEnumValues(HKEY hKey)

では、列挙のプロセスを詳しく見ていきましょう。考え方としては、最初に RegQueryInfoKey (bit.ly/2jraw2H) API を呼び出して、指定したレジストリ キーの値の総数や値の名前の最大長など、いくつかの列挙前に必要な情報を取得します (図 1 参照)。

図 1 RegQuery­InfoKey API の呼び出し

DWORD valueCount{};
DWORD maxValueNameLen{};
LONG retCode = ::RegQueryInfoKey(
  hKey,
  nullptr,    // No user-defined class
  nullptr,    // No user-defined class size
  nullptr,    // Reserved
  nullptr,    // No subkey count
  nullptr,    // No subkey max length
  nullptr,    // No subkey class length
  &valueCount,
  &maxValueNameLen,
  nullptr,    // No max value length
  nullptr,    // No security descriptor
  nullptr     // No last write time
);

今回必要のない情報には nullptr を渡します。当然、前述の API を呼び出すときは、戻り値をチェックし、問題があれば例外をスローします。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot query key info from"
    the registry", retCode};
}

Windows デベロッパー センターの RegQueryInfoKey 関数のページ (bit.ly/2lctUDt) によると、値の名前の最大長として返されるサイズ (上記のコードでは変数 maxValueNameLen に格納) は、終端の NUL を含みません。そのため、値の名前を読み取るバッファーを割り当てるときは、終端の NUL を考慮して、返されたサイズの値に 1 を加算します。

maxValueNameLen++;

次に、正しいサイズのバッファーを割り当て、列挙の各ステップで値の名前を読み取ります。この目的には、オーバーヘッドが少なく、効率的な std::unique_ptr<wchar_t[]> を使用できます。

auto nameBuffer = std::make_unique<wchar_t[]>(maxValueNameLen);

列挙の結果は、値の名前と型のペアという形式で、std::vector に格納します。

std::vector<std::pair<std::wstring, DWORD>> values;

列挙のプロセス中、コンテンツをこのベクトルに段階的に追加し、その後、列挙の完了時に呼び出し元に "values" を返します。

その後、for ループを使用して、RegEnumValue API を繰り返し呼び出し、各反復ステップで新しい値を列挙します。

for (DWORD index = 0; index < valueCount; index++)
{
  // Call RegEnumValue to get data of current value ...
}

valueCount は、列挙前最初の RegQueryInfoKey 呼び出しから取得しています。

for ループ本体内では、RegEnumValue API を呼び出して、現在値について目的の情報を取得します。今回必要な情報は、値の名前と型です。値の名前は先ほど割り当てた nameBuffer に読み取り、値の型はシンプルな DWORD に格納します。したがって、for ループ本体内のコードは、以下のようになります。

DWORD valueNameLen = maxValueNameLen;
DWORD valueType{};
retCode = ::RegEnumValue(
  hKey,
  index,
  nameBuffer.get(),
  &valueNameLen,
  nullptr,    // Reserved
  &valueType,
  nullptr,    // Not interested in data
  nullptr     // Not interested in data size

いつものように、API の戻り値をチェックし、エラーが発生したら例外をスローします。

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot get value info from the registry", retCode};
}

正常終了すると、RegEnumValue API は、指定された nameBuffer に値の名前を、変数 valueType に値の型を書き込みます。そこで、これら 2 つの情報を含む pair<wstring, DWORD> を構築し、この情報のペアを列挙結果のベクトルに追加します。

values.push_back(std::make_pair(
  std::wstring{ nameBuffer.get(), valueNameLen },
  valueType
));

for ループの後、結果の "values" ベクトルを呼び出し元に返します。

return values;

呼び出し元は、その後、以下のように C++ ラッパー関数を呼び出すだけで、レジストリ キーのすべての値を列挙できます。

auto values = RegEnumValues(hKey);
// For each value
for (const auto& v : values)
{
  // Process v.first (value's name) and v.second (value's type)
  // ...
}

同様のコーディング パターンを使って、特定のレジストリ キーのサブキーも列挙できます。今回の場合は、Win32 RegEnumKeyEx (bit.ly/2k3VEX8) API を前述の RegEnumValue の代わりに使用します。このようなサブキー列挙関数のコードは、本稿付属のコード サンプルに含めています。

HKEY ロウ ハンドル用の安全なリソース マネージャー

HKEY Win32 ロウ ハンドル型で表現されるレジストリ キーは、C++ のリソース マネージャー クラスに安全かつ適切にラップできます。このクラスのデストラクターは、ラップされたロウ ハンドルの RegCloseKey API を適切に呼び出して、そのハンドルを自動的に閉じます。さらに、移動のコンストラクターや移動の代入演算子のような移動を意味する操作を定義して、C++ のリソース マネージャークラスの異なるインスタンスの間で、ラップしたハンドルの所有権を効率よく移動できます。効率化のため、例外をスローしないすべてのクラス メソッドは noexcept とマークが付けられており、C++ コンパイラが出力するコードが最適化されます。この便利な主要リソース マネージャー C++ クラスは RegKey という名前で、本稿付属の Registry.hpp ファイルに実装しています。この再利用可能なヘッダーのみのファイルには、2 つのヘルパー関数も実装しています。 この 2 つは RegOpenKey と RegCreateKey という関数で、それぞれ Win32 API の RegOpenKeyEx と RegCreateKeyEx をラップして、前述の C++ リソース マネージャー クラスに問題なくラップされる HKEY ハンドルを返します。エラーが発生した場合、これらの C++ 関数は RegistryError 例外をスローし、ロウ C インターフェイス Win32 API が返すエラー コードをラップします。

まとめ

RegGetValue Win32 API は、Windows レジストリから値を読み取るために、RegQueryValueEx などの低レベルな API に比べて高度なインターフェイスを提供します。RegGetValue には、返される文字列が正しく NUL 終端されるようにするなど、より安全なインターフェイスも提供します。とは言え、RegGetValue は C インターフェイスの低レベルの API なので、プログラマは多くの詳細に注意を払い、バグが起きやすい複雑なコードにならないようにプログラミングします。今回は、便利で、使いやすく、誤用の少ない最新の C++ インターフェイスを構築して、RegGetValue API の複雑さを隠ぺいすると同時に、Windows レジストリへのアクセスを簡略化する方法を取り上げました。また、さらに高度な C++ 関数に RegEnumValue API をラップして、特定のレジストリ キーの値をすべて列挙しました。今回紹介した関数とクラスの実装を含むソース コードは、コード サンプルのダウンロードから、再利用可能なヘッダーのみの形式 (Registry.hpp 内) で見つかります。


Giovanni Dicanio は、C++ と Windows OS を専門とするコンピューター プログラマであり、Pluralsight (bit.ly/GioDPS、英語) の執筆者です。また、Visual C++ MVP でもあります。プログラミングとコース作成の傍ら、C++ 専門のフォーラムやコミュニティで他の開発者をサポートしています。彼の連絡先は、giovanni.dicanio@gmail.com (英語) です。また、msmvps.com/gdicanio (英語) でブログも公開しています。

この記事のレビューに協力してくれた技術スタッフの David Carvey と Marc Gregoire に心より感謝いたします。
David Cravey は、GlobalSCAPE のエンタープライズ アーキテクトです。いくつかの C++ ユーザー グループのまとめ役であり、4 度 Visual C++MVP を受賞しています。

Marc Gregoire はベルギー出身のシニア ソフトウェア エンジニアであり、Belgian C++ Users Group の創設者であり、『Professional C++』(Wiley、2014 年) の著者であり、『C++ Standard Library Quick Reference』(Apress、2016 年) の共著者でもあります。さらに、彼はさまざまな書籍のテクニカル ディレクターを務め、2007 年以降は、彼の VC++ の専門知識について MVP を毎年受賞しています。彼の連絡先は、marc.gregoire@nuonsoft.com (英語のみ) です。