Create  Edit  Diff  FrontPage  Index  Search  Changes  Login

The Backyard - BasicString Diff

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

!BSTRとは

BSTRは、Visual BASICの文字列型の実装に利用されている構造体だ。
定義は、wtypes.hにある。

typedef /* [wire_marshal] */ OLECHAR __RPC_FAR *BSTR;

これだけ見ると、単なるOLECHARへのポインタに見える。ちなみにOLECHARも同じくwtypes.hにある。

typedef WCHAR OLECHAR;

さらにWCHARは、同じくwtypes.hに

typedef wchar_t WCHAR;

と定義されている。

が、これはBSTRの実体の半分しか映していない。残り半分は、同じくwtypes.hの次の定義である。

typedef struct  _WORD_BLOB
    {
    unsigned long clSize;
    /* [size_is] */ unsigned short asData[ 1 ];
    } WORD_BLOB;
...
typedef /* [unique] */ FLAGGED_WORD_BLOB __RPC_FAR *wireBSTR;

すなわち、4バイト符号なし整数と、その値を要素数とした(実際には正確ではない。後述)16ビット符号なし整数の配列だ。しかも、それはBLOB――バイナリラージオブジェクトと名前付けられていることから理解できるように文字ではなく単なるバイナリーである。

仮に自動変数としてBSTRを作成するのであれば、次のコードとなる。

WCHAR auto_bstr[] = L"\0\0This is BSTR !"; // 10/14 修正
BSTR bstr = &auto_bstr[2];
*(unsigned long*)auto_bstr = wcslen(bstr);

単純なWCHAR* ではなく、あくまでも先頭から4バイト戻った位置に文字数を格納した構造体なのである。

BSTRには、さらにルールがある。

#空文字列はNULL
#実体としてのBSTRはNULLエンドセンチネルを持つ
#文字列内にL'\0'を保持できる
#BLOBとして利用しても良い(UTF-16とは限らない)

結局、BSTRは、VBのString型であり、COMのインターフェイス用の特殊な型なのである。

このため、CやC++でBSTRを扱う場合には、単純にwchar_t*として処理してはならない。

特に、wcslenやwcscmpは、引数としてNULLを与えるとクラッシュするため、BSTRを直接与えることは厳禁である。以下のいずれかのコードを利用しなければならない。

// NULLなら、ダミーの空文字列に置換
int len = wcslen((bstr) ? bstr : L"");
// API
if (SysStringLen(bstr) > 0 && wcscmp(bstr, L"比較対象"))

! BSTR API

:SysAllocString:引数で指定した文字数のBSTRを作成する
:SysFreeString:引数で指定したBSTRを削除する
:SysStringLen:引数で指定したBSTRの文字数を返す
:SysStringByteLen:引数で指定したBSTRのバイト数を返す
:SysAllocStringByteLen:引数で指定した8ビット文字列を指定したバイト数コピーしたBSTRを作成する
:SysAllocStringLen:引数で指定した16ビット文字列を指定した文字数コピーしたBSTRを作成する

これらのAPIは、すべてNULLを空文字列として正しくハンドリングする。また、文字数やバイト数を求めるAPIは、文字配列部分の長さだけを扱い、エンドセンティネルや先頭の長さインディケータを含まない。

!!バグをつつかないこと

以上の特性から、2つのプログラムを作成し、プロセス間通信でバイナリー値を扱いたい場合に、容易に次のようなコードを考えつく。

BYTE buff[MAX_DATA];
ZeroMemory(buff, MAX_DATA);
buff[0] = '\0';
...
// SysAllocStringByteLenは引数内の\0を無視する。
BSTR b = SysAllocStringByteLen(reinterpret_cast<LPCSTR>(buff), MAX_DATA);
outproc->com_method(b);
SysFreeString(b);

このコードは、大体、正しく動く。しかし、LRPCの途中にバグがある可能性は常に付いて回る。すなわち、途中に一箇所でも

BSTR new_bstr = SysAllocString(wcslen(input));
wcscpy(bstr, input);

といったコードがあれば、もしデータ中に2バイト連続したNULLがあれば、そこまでしかコピーされないからだ。そして、そのようなコードはCOMの中にも存在したことがある(したがって、現時点でも存在する可能性がある)。

バイナリーデータをCOMで安全に送受するには、SAFEARRAYSafeArrayを使用するか、またはカスタムインターフェイスを作成すべきである。