SysAllocStringとSysFreeStringでBSTRを安全に管理する
Windows APIやCOM連携では、文字列を単なるwchar_t*として扱わず、Automation用の文字列型であるBSTRとして受け渡す場面があります。BSTRはメモリ上の形式と所有権の規則が決まっているため、生成した関数と解放する関数の対応を理解しておくことが重要です。
SysAllocStringで確保したBSTRは、原則として利用が終わった側がSysFreeStringで解放します。戻り値、引数、構造体のメンバーなど、どこで所有権が移動するかを整理すれば、メモリリークや二重解放を避けながらCOM対応コードを実装できます。
BSTRの正体と用途
BSTRはOLECHAR*として見える文字列型ですが、通常のヌル終端文字列とは異なり、文字列の直前にバイト長を保持する領域があります。そのため、文字列本体の先頭ポインターを受け取ったコードは、必要に応じて長さ情報を参照できます。Windows環境ではUnicode文字列として扱われ、COM AutomationやActiveXのインターフェイスで広く利用されます。
BSTRの終端には通常、ヌル文字が置かれます。ただし、長さ情報を別に持つ設計のため、埋め込みヌルを含むデータも表現できます。単純なwcslenだけで長さを判断すると、途中で処理が切れる可能性があるため、BSTRを扱うAPIでは仕様上の文字数やバイト数を確認する必要があります。
SysAllocStringの割り当て規則
SysAllocStringは、ヌル終端されたワイド文字列を受け取り、BSTRとして管理できるメモリを確保します。宣言は概ね次の形です。
BSTR SysAllocString(const OLECHAR* psz);
成功すれば新しいBSTRを返し、メモリ不足などで失敗した場合はnullptrを返します。入力がnullptrの場合の扱いを呼び出し側の想定だけで決めず、対象環境のAPI仕様を確認し、必要なら空文字列を明示的に渡す設計にすると意図が明確になります。
埋め込みヌルや終端を含まない固定長データをBSTRへ変換する場合は、SysAllocStringLenが適しています。この関数は文字数を明示できるため、通常の終端文字列を前提にするSysAllocStringとの使い分けが重要です。
SysFreeStringで所有権を閉じる
SysFreeStringは、BSTR用のアロケータで確保された文字列を解放します。BSTRを利用し終えたら、対応する所有者が次のように解放します。
BSTR text = SysAllocString(L"sample");
if (text != nullptr) {
// textを利用する
SysFreeString(text);
}
SysFreeString(nullptr)は安全に呼び出せるため、エラー処理でポインターを初期化しておくと後片付けを一本化できます。解放した後の変数を再利用する可能性があるなら、nullptrを代入して無効なポインターを残さないようにします。
LocalFree、GlobalFree、delete[]など、別のメモリ管理関数でBSTRを解放してはいけません。逆に、スタック上の配列や文字列リテラルをBSTRとして扱い、そのアドレスにSysFreeStringを適用することも危険です。確保方法と解放方法は必ず対にします。
COM境界での受け渡し
COMメソッドの引数にBSTRが登場する場合、[in]、[out]、[in, out]の属性によって所有権の扱いが変わります。[in] BSTRは呼び出し中だけ参照する契約が多く、受け取った側が勝手に解放してはいけません。一方、[out] BSTR*では、呼び出された側が新しいBSTRを生成し、呼び出し側が解放する設計が一般的です。
たとえばHRESULT GetName(BSTR* value)のような関数では、開始時に*value = nullptrとしてから、SysAllocStringの結果を格納します。呼び出し側は成功・失敗にかかわらず、受け取った値が非ヌルならSysFreeStringで処理します。複数のCOMメソッドを連続して使う場合は、既存のBSTRを上書きする前に古い値の所有者を確認してください。
メッセージ処理やウィンドウプロシージャからCOM文字列を渡す場合も、引数の寿命を越えてポインターを保存しないことが基本です。メッセージ引数の解釈と所有権の整理には、WindowProcの実装に関する知識も役立ちます。
失敗時と例外時の安全策
BSTRの確保は失敗する可能性があります。SysAllocStringの戻り値を無条件に利用せず、nullptrを検査してから処理を進めます。COMメソッドなら、メモリ不足を表すE_OUTOFMEMORYなどのHRESULTへ変換して返す設計にすると、呼び出し側が原因を判断しやすくなります。
C++の例外が途中で発生すると、手動解放を記述した箇所へ到達しないことがあります。CComBSTRや_bstr_tなどのRAIIラッパーを利用できる環境では、スコープ終了時の自動解放を選択できます。ただし、所有権を生ポインターへ移す処理では、ラッパー側が解放するのか呼び出し側が解放するのかを明確にします。
Windows APIコードへの組み込み
BSTRを名前付きパイプや独自IPCで送信する場合、BSTRのポインターをそのまま送ることはできません。送信先プロセスではアドレス空間が異なるため、長さを取得して文字データをバイト列として転送し、受信側で新しいBSTRを生成します。プロセス間通信の設計を確認する際は、名前付きパイプ通信のようなストリーム処理の考え方も参考になります。
画面描画やウィンドウメッセージの処理でBSTRを作る場合も、描画APIのリソース管理と文字列の管理を混同しないことが大切です。BeginPaintとEndPaintは描画状態のための対になったAPIであり、BSTRの解放とは別の責務です。BeginPaintとEndPaintのように、各APIが管理するリソース単位を分けて考えると、後処理漏れを見つけやすくなります。
実装時に確認したいポイント
BSTRを扱うコードは、短い処理でも「誰が確保し、誰が解放するか」をコメントやインターフェイス定義に残すと安全です。特に、C言語形式のAPIとCOM形式のAPIを橋渡しする箇所では、wchar_t*、LPWSTR、BSTRを同じものとして扱わないようにします。
実装前には、次の項目を確認すると管理規則を整理できます。
SysAllocStringとSysFreeStringの対応が明確になっているSysAllocStringやSysAllocStringLenの戻り値を検査している[in]、[out]、[in, out]ごとの所有権を定義しているdelete[]やLocalFreeでBSTRを解放していない- 例外やHRESULTエラー時にも解放処理が実行される
- 解放後のBSTRポインターを再利用しない、または
nullptrにしている
文字列を構造体や非同期処理へ保存する場合は、元のBSTRを借用しているだけなのか、複製して所有しているのかを区別します。必要な期間が長いならSysAllocStringでコピーを作り、処理の終了地点で確実に解放する設計が扱いやすくなります。
BSTR管理の要点は、特殊な文字列形式を正しく作ることだけではありません。メモリの確保元、受け渡し時の契約、エラー時の経路、解放する責任者を一貫させることが本質です。まずはBSTR変数を初期値nullptrで用意し、成功した確保だけを所有者がSysFreeStringで閉じる形から実装すると、Windows APIとCOMの境界でも安全な文字列処理を保てます。