COMの文字列にはBSTR
というデータ型が使われています。
BSTR
が定義されているWindows SDKのヘッダーを見てみましょう。
// <wtypes.h> in Windows SDK 8.1 #ifndef _PREFAST_ typedef /* [wire_marshal] */ OLECHAR *BSTR; #else // _PREFAST_ typedef _Null_terminated_ OLECHAR *BSTR; #endif
OLECHAR
はもともとCOMの前身であるOLE (Object Linking and Embedding) で使われていた文字型の名残で、当時はANSI MBCSのchar
でしたが、現在はWCHAR
すなわちwchar_t
と等価です。Windows上ではsizeof(wchar_t) == 2
であり、UTF-16エンコーディングのUnicode文字を表現するのに使われます。
// <WTypesbase.h> in Windows SDK 8.1 #if defined(_WIN32) && !defined(OLE2ANSI) typedef WCHAR OLECHAR; typedef /* [string] */ __RPC_string OLECHAR *LPOLESTR; typedef /* [string] */ __RPC_string const OLECHAR *LPCOLESTR; #define OLESTR(str) L##str #else typedef char OLECHAR; typedef LPSTR LPOLESTR; typedef LPCSTR LPCOLESTR; #define OLESTR(str) str #endif
「ふーん、じゃあBSTR
はwchar_t
へのポインタなんだね。てことは、BSTR
を要求するAPIにはwchar_t
へのポインタを渡してもいいんだ!」
ダメです。絶対にやめてください。
そもそもBSTRとは?
BSTRはCOMだけでなく旧VB/VBAでも使われている、長さ情報を持った自己記述的文字列型の一種です。Windows SDKの <oaidl.h> で定義されているVARIANT
型とも深い関わりがあります。
BSTRのデータ構造に関しては、以下の資料をよく読めばイメージできるはずです。
実際にコードで試してみます。
#define NOMINMAX #include <Windows.h> #include <tchar.h> #include <cstdio> #include <clocale> #include <initializer_list> int main() { _tsetlocale(LC_ALL, _T("")); for (auto str : { L"abc", L"hoge", L"Hello" }) { // BSTR の型自体は OLECHAR* すなわち wchar_t* の typedef エイリアスだが、 // 実際の BSTR データ構造は先頭に4バイトの長さ情報(符号無し整数)を持っている。 // Pascal の自己記述型文字列と、C の NUL 終端文字列の両方をミックスしたような構造。 // SysAllocString() は、終端 NUL 文字の分を加味した (4 + UTF-16 文字列バイト長 + 2) の長さを持つバイト配列を確保し、 // 5バイト目以降へのポインタを BSTR 型として返却しているにすぎない。 // SysFreeString() は、渡されたポインタからマイナス方向に4バイトオフセットした位置を先頭とするバイト配列を解放する。 // SysStringByteLen() や SysStringLen() は、渡されたポインタからマイナス方向に4バイトオフセットした位置に長さ情報が格納されていると仮定して値を読み取っている。 // これにより、SysStringByteLen() と SysStringLen() の計算量オーダーは O(1) となる。 // したがって、BSTR を要求する API に wchar_t* を渡してはいけない。 // const wchar_t* や const wchar_t[] を BSTR にキャストするのもダメ。 BSTR bstr = ::SysAllocString(str); const UINT sizeInBytes = *(reinterpret_cast<UINT32*>(bstr) - 1); const UINT sysByteLen = ::SysStringByteLen(bstr); const UINT sysStrLen = ::SysStringLen(bstr); wprintf(L"BSTR: wsz = \"%ls\", size = %u[bytes], byte-len = %u, len = %u\n", bstr, sizeInBytes, sysByteLen, sysStrLen); ::SysFreeString(bstr); bstr = nullptr; } // BSTR を偽装してみる。通常はマネしないように。 // 本来の BSTR データ構造であれば、リトルエンディアンのバイト長として (2 * 3) == 6 が格納されていなければならない。 // また、バイト長は2の倍数すなわち偶数でなければならない。 struct { INT32 len; wchar_t str[128]; } dummyData = { 65535, L"abc" }; BSTR bstrIllegal = dummyData.str; const UINT sysByteLen = ::SysStringByteLen(bstrIllegal); const UINT sysStrLen = ::SysStringLen(bstrIllegal); wprintf(L"Illegal BSTR: byte-len = %u, len = %u\n", sysByteLen, sysStrLen); }
以上のように、BSTRはメモリの確保/解放や長さ取得に専用のAPIを利用します。
BSTRをC/C++で読み取る際はwchar_t
のゼロ終端文字列として扱うこともできますが、その逆は不可です。決してwchar_t
のゼロ終端文字列をBSTRとして扱ってはいけません。未定義の異常動作を引き起こす原因になります。
BSTRのラッパー
Visual C++ではATL::CComBSTR
や_bstr_t
といったラッパークラス型(スマートポインタ)が用意されており、ライフサイクル管理の煩雑なBSTRを比較的簡単に扱うことができます。
WIL (Windows Implementation Library) にもwil::unique_bstr
が用意されています。
- Programming with CComBSTR (ATL) | Microsoft Docs
- CComBSTR Class | Microsoft Docs
- _bstr_t class | Microsoft Docs
- C++/WinRT での COM コンポーネントの使用 - UWP applications | Microsoft Docs
ただし、BSTRラッパーの内部では結局システムAPIをコールするので、ヒープ管理にCRT (C Runtime) を利用するATL::CStringW
や標準C++のstd::wstring
と違い、速度やメモリ効率などのパフォーマンス面で不利です。
BSTRラッパーはBSTRを要求するCOMコンポーネントとの境界面 (API) にのみ使用し、普段は文字列の管理にATL::CStringW
やstd::wstring
を使うべきです。