この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。
27. 狡猾な BSTR 文字列
もう 1 つ注意が必要なデータ型 BSTR (基本文字列またはバイナリー文字列) について述べます。
VirtualBox* プロジェクトから抜粋した以下のコードについて考えてみます。このコードにはエラーが含まれています。PVS-Studio アナライザーは、次の診断を出力します。
V745 A ‘wchar_t *’ type string is incorrectly converted to ‘BSTR’ type string. Consider using ‘SysAllocString’ function. (V745 ‘wchar_t *’ 型の文字列から ‘BSTR’ 型の文字列への不正な変換です。’SysAllocString’ 関数を使用することを検討してください。)
.... HRESULT EventClassID(BSTR bstrEventClassID); .... hr = pIEventSubscription->put_EventClassID( L"{d5978630-5b9f-11d1-8dd2-00aa004abd5e}");
説明
BSTR は次のように宣言されています。
typedef wchar_t OLECHAR; typedef OLECHAR * BSTR;
一見 “wchar_t *” と BSTR は同じように見えますが、実際には異なります。これは、多くの混乱とエラーを招きます。
上記の例をよく理解するため、BSTR 型について説明します。
MSDN* にこの型の説明 (英語) があります。MSDN* ドキュメントを読むことはあまり楽しいことではないかもしれませんが、必要な作業です。
BSTR (基本文字列またはバイナリー文字列) は、COM、自動化、および相互運用関数で使用される文字列データ型です。BSTR データ型は、スクリプトからアクセスされるすべてのインターフェイスで使用できます。以下は、BSTR の説明です。
- 長さプリフィクス。データ文字列のバイト数を示す 4 バイトの整数です。データ文字列の最初の文字の直前にあります。この値に終端 null 文字は含まれません。
- データ文字列。Unicode 文字の文字列です。複数の null 文字を持つ可能性があります。
- 終端子。2 つの null 文字です。
BSTR はポインターです。ポインターは、長さプリフィクスではなく、データ文字列の最初の文字をポイントします。BSTR は、COM メモリー割り当て関数を使用して割り当てられるため、メモリー割り当てを考慮せずにメソッドから返すことができます。次のコードは不正です。
BSTR MyBstr = L"I am a happy BSTR";
このコードは、問題なくビルド (コンパイルおよびリンク) されますが、文字列に長さプリフィクスがないため、正しく機能しません。デバッガーを使用してこの変数のメモリー位置を調査すると、データ文字列の先頭に 4 バイトの長さプリフィクスがないことが分かります。代わりに、次のコードを使用します。
BSTR MyBstr = SysAllocString(L"I am a happy BSTR");
デバッガーでこの変数のメモリー位置を調査すると、値 34 が格納された長さプリフィクスを確認できます。これは、”L” 文字列修飾子があることから、ワイド文字の文字列に変換される 17 シングルバイト文字の文字列で想定される値ですさらにデバッガーでは、データ文字列の後ろに 2 バイトの終端 null 文字 (0x0000) があることも分かります。
BSTR 型の引数を想定している COM 関数に単純な Unicode 文字列を渡すと、COM 関数は失敗します。
これで、BSTR と “wchar_t *” 型の単純な文字列を区別すべき理由がお分かりいただけたでしょう。
関連情報
- MSDN*: BSTR (英語)
- Stack Overflow: BSTR への wchar_t* の引き渡しを検出する静的コード解析 (英語)
- Stack Overflow: BSTR から std::string (std::wstring) への変換とその逆 (英語)
- Robert Pittenger: BSTR と CString 変換のガイド (英語)
- Eric Lippert: Eric の BSTR セマンティクスの完全ガイド (英語)
正しいコード
hr = pIEventSubscription->put_EventClassID( SysAllocString(L"{d5978630-5b9f-11d1-8dd2-00aa004abd5e}"));
推奨事項
ここで紹介するヒントは、前述のヒントに似ています。不明な型がある場合は、すぐに作業に着手するのではなく、ドキュメントで確認します。これは重要なことなので、繰り返し述べても問題ないでしょう。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。