【MFC】CString のメモリ管理: GetBuffer / ReleaseBuffer の作法

【MFC】CString のメモリ管理: GetBuffer / ReleaseBuffer の作法

Win32 API に LPTSTR(書き込み可能な文字列ポインタ)を渡す場面で、CString の内部バッファをそのまま使いたくなることがあります。
この記事では、GetBuffer / ReleaseBuffer正しい使い方と、やってはいけないパターンを解説します。


目次

なぜ GetBuffer が必要なのか

CString は内部でバッファの参照カウントやサイズを管理しています。operator LPCTSTR() で取得できるポインタは const であり、直接書き換えることは想定されていません。

Win32 API の中には、引数に書き込み用のバッファポインタを要求するものが多数あります。たとえば GetWindowTextGetModuleFileNameGetEnvironmentVariable などです。こうした API に CString を渡すには、GetBuffer で書き込み可能なポインタを取得する必要があります。


基本パターン:GetBuffer → API 呼び出し → ReleaseBuffer

手順は 3 ステップです。

CString strPath;

// 1. 必要な文字数分のバッファを確保して、書き込み可能ポインタを取得
LPTSTR pBuf = strPath.GetBuffer(MAX_PATH);

// 2. API にバッファを渡す
DWORD len = GetModuleFileName(NULL, pBuf, MAX_PATH);

// 3. CString に制御を返す(内部の長さ情報を再計算させる)
strPath.ReleaseBuffer(len);

// 以降は通常の CString として使える
AfxMessageBox(strPath);

GetBuffer(nMinBufLength) は、最低でも nMinBufLength 文字を格納できるバッファを返します。ここで指定する値に終端 '\0' のぶんは含みません。ReleaseBuffer は、書き換え後の長さを CString 側へ反映するための呼び出しです。


ReleaseBuffer を呼ばないとどうなるか

GetBuffer で取得したポインタ経由で内容を変更したあとは、ReleaseBuffer を呼んでから他の CString メソッドを使う必要があります。Microsoft Learn でも、他の CSimpleStringT メソッドを使う前に ReleaseBuffer で内部状態を更新することが前提になっています。

CString str;
LPTSTR p = str.GetBuffer(256);
lstrcpy(p, _T("test"));

// NG: ReleaseBuffer を呼ばずに他の操作をする
str += _T(" suffix");

これは単なる作法ではなく、ドキュメント上の契約です。ReleaseBuffer を飛ばすと長さ情報が更新されず、その後の動作は実装依存になります。例外経路も含めて、必ず GetBuffer と対で扱ってください。


ReleaseBuffer の引数

ReleaseBuffer には引数を渡すことができます。

呼び出し方動作
ReleaseBuffer()(引数なし)バッファ内の '\0' を基準に長さを決める
ReleaseBuffer(n)長さを n 文字として反映する

API が戻り値で書き込んだ文字数を返す場合は、ReleaseBuffer(len) のように明示的に渡す方が効率的です。'\0' の検索が不要になります。

デバッガでも、ReleaseBuffer() 後に str と内部バッファの内容が同期していることを確認できます。

Visual Studio のウォッチウィンドウで CString の内部バッファを確認している画面

GetBufferSetLength との違い

GetBufferSetLength(n) は、バッファをちょうど n 文字ぶんにそろえてからポインタを返します。終端文字の自動探索に頼らず、最終的な文字数を自分で管理したいときに使います。こちらも、返されたポインタ経由で内容を書き換えたなら、他の CString メソッドを使う前に ReleaseBuffer を呼ぶ必要があります。


よくある間違い

間違い 1:GetBuffer のサイズ指定が小さすぎる

CString str;
LPTSTR p = str.GetBuffer(10);
// API が 10 文字以上書き込む可能性がある → バッファオーバーラン
GetWindowText(hWnd, p, 256);  // ← 危険
str.ReleaseBuffer();

GetBuffer に渡すサイズは、API が書き込む最大文字数以上でなければなりません。

間違い 2:GetBuffer で取得したポインタを保持し続ける

CString str(_T("Hello"));
LPTSTR p = str.GetBuffer(256);
str.ReleaseBuffer();

// NG: ReleaseBuffer 後も p を使い続ける
// CString が再割り当てすると p は無効なアドレスになる
lstrcpy(p, _T("Danger"));  // ← 未定義動作

ReleaseBuffer の後に CString がバッファを再割り当てすると、以前のポインタは無効になります。GetBuffer で取得したポインタは ReleaseBuffer までの間だけ使ってください。

間違い 3:LPCTSTR で取得したポインタを const_cast する

CString str(_T("Hello"));
// NG: const を外して書き込む
LPTSTR p = const_cast<LPTSTR>((LPCTSTR)str);
lstrcpy(p, _T("World"));

LPCTSTR で得たポインタは読み取り専用として扱う前提です。const_cast で無理に書き込むと CString の内部管理を壊します。書き込みが必要なら、必ず GetBuffer を経由してください。


実務での使用例

レジストリから文字列を読む

CString ReadRegString(HKEY hKey, LPCTSTR valueName)
{
    DWORD cbData = 0;
    LSTATUS rc = RegGetValue(
        hKey, NULL, valueName,
        RRF_RT_REG_SZ,
        NULL, NULL, &cbData);
    if (rc != ERROR_SUCCESS || cbData == 0)
        return CString();

    CString result;
    LPTSTR p = result.GetBuffer(cbData / sizeof(TCHAR));
    rc = RegGetValue(
        hKey, NULL, valueName,
        RRF_RT_REG_SZ,
        NULL, p, &cbData);
    if (rc != ERROR_SUCCESS)
    {
        result.ReleaseBuffer(0);
        return CString();
    }

    result.ReleaseBuffer();
    return result;
}

RegQueryValueExREG_SZ でも null 終端を保証しません。文字列として読む用途なら、終端文字を検査してくれる RegGetValue の方が安全です。

環境変数を取得する

CString GetEnv(LPCTSTR varName)
{
    CString result;
    DWORD need = GetEnvironmentVariable(varName, NULL, 0);
    if (need > 0)
    {
        LPTSTR p = result.GetBuffer(need);
        GetEnvironmentVariable(varName, p, need);
        result.ReleaseBuffer();
    }
    return result;
}

実行すると、PATH の先頭部分と全体の文字数を確認できます。GetEnvironmentVariable は必要サイズを「終端 '\0' を含む文字数」で返すため、この 2 段階取得と相性が良い API です。

GetBuffer と ReleaseBuffer の実行結果がコンソールに表示されている画面

まとめ

  • GetBuffer は Win32 API に CString の書き込み用バッファを渡すための正規の手段です
  • ReleaseBufferGetBuffer / GetBufferSetLength と必ずセットで呼んでください。呼び忘れると長さ情報が更新されません
  • GetBuffer に渡すサイズは、API が書き込む最大文字数以上を指定してください
  • LPCTSTRconst_cast して書き込むのは禁止です。必ず GetBuffer を経由してください
目次