BSTR 覚え書き

消えると困るのでコピペ


コピペ元

BSTR 覚え書き
2002.2.19 kanegon create
2002.2.23 kanegon update


BSTR は見かけ上(Windowsヘッダ上)、LPOLESTR と等しい。

   typedef WCHAR                     OLECHAR;    // ※
   typedef OLECHAR __RPC_FAR *       LPOLESTR;
   typedef const OLECHAR __RPC_FAR * LPCOLESTR;

   ※ 16bit Platform では OLECHAR == char

しかし、実際には先頭に文字列長さを保持しており、この2つを混同しないこと。
BSTR のポインタはメモリ上の先頭を指していないため、BSTR を要求する関数に
LPOLESTR を渡してはならない。また、その関係で BSTR のメモリ管理は完全に別物
である。通常のメモリアロケート関数で処理しないこと。
ただし、LPOLESTR を要求する関数に BSTR を渡すことは問題ない。

       LPOLESTR
          ↓
          ┌──────────┬─┐
          │ string             │\0│
          └──────────┴─┘

         BSTR
          ↓
  ┌───┬──────────┬─┐
  │length│ string             │\0│
  └───┴──────────┴─┘
   ※ lentth は終端 '\0' を含まない string の byte 数

BSTR の領域の取得/解放は SysAllocString()/SysFreeString() または
その関連 API を使用して行なう。

    BSTR SysAllocString(const OLECHAR* psz);
    BSTR SysAllocStringLen(const OLECHAR* psz, UINT len);
    BSTR SysAllocStringByteLen(LPCSTR psz, UINT len);
    INT SysReAllocString(BSTR* pbstr, const OLECHAR* psz);
    INT SysReAllocStringLen(BSTR* pbstr, const OLECHAR* psz, UINT len);
    void SysFreeString(BSTR bstr);

    UINT SysStringByteLen(BSTR bstr);
    UINT SysStringLen(BSTR bstr);

    ※ このあたり、パラメタおよび戻り値の型が MSDN とヘッダで軒並み異なって
       いる。仕様変更でもあったのか?
       SysReAllocString() の戻り値は何者?

ここで、char* から BSTR の変換には以下を使用できるように思われる。

    BSTR str = ::SysAllocStringByteLen(psz, strlen(psz) * sizeof(OLECHAR));

しかし、この関数は第一パラメタのバイナリ列を BSTR にそのままコピーするだけで
ANSI-UNICODE 変換とかするわけでないのでこの目的には使えない。注意すること。

通常の VC++ のコーディングでは、このあたりの変換はマクロやラッパクラスを使っ
て行なうが、API のみで実装する場合、以下のような感じになる(たぶん)。

   [char* => BSTR]
   size = ::MultiByteToWideChar(CP_ACP, 0, psz, -1, NULL, 0) - 1;
   bstr = ::SysAllocStringByteLen(NULL, size * sizeof(OLECHAR));
   ::MultiByteToWideChar(CP_ACP, 0, psz, -1, bstr, size);

   [BSTR => char*]
   size = ::WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL) - 1;
   psz = new char[size + 1];
   ::WideCharToMultiByte(CP_ACP, 0, bstr, -1, psz, size + 1, NULL, NULL);


また、MSDN には情報が見つからなかったが、便利なユーティリティ関数も存在して
いる。以下は _bstr_t クラスなどの内部処理で使われているものである。

    BSTR bstr = _com_util::ConvertStringToBSTR(psz);   // throw(_com_error)
	char* psz = _com_util::ConvertBSTRToString(bstr);  // throw(_com_error)

これを使うと変換が1行で記述でき、かなり便利である。

# もちろん _bstr_t の方がもっと便利である。

このユーティリティ関数で確保した領域の解放にはそれぞれ SysFreeString()、
delete を使用するみたい。

    SysFreeString(bstr);
    delete[] psz;


同様に VC++ のヘッダを見ていくと、BSTR から BSTR の生成では、

    BSTR str = ::SysAllocStringByteLen(reinterpret_cast<char*>(bstr),
                                       ::SysStringByteLen(bstr));

を使用している。
単純に SysAllocString() を使うのに比べると、効率悪そうだが、
SysAllocStringByteLen() の説明に

   If psz is Null, a string of the requested length is allocated, but
   not initialized. The string psz can contain embedded null characters,
   and does not need to end with a Null. Free the returned string later
   with SysFreeString.

とあり、BSTR は途中に NULL 文字を含んだ文字列を扱うことができるため、おそらく
その対処と思われる。

# SysAllocString() は終端 NULL の標準文字列しか扱えない。

このように途中に NULL 文字を含み、長さ情報を自前で保持する BSTR であるが、
終端の NULL も明確に保証されている。BSTR の領域を終端 NULL の LPOLESTR とし
て使用するのは問題ない。
SysAllocStringByLen() の長さパラメタの説明には

   Number of bytes to copy from psz. A null character is placed afterwards,
   allocating a total of len+1 bytes. 

と記述がある。

さらに、元になる文字列がない場合の領域作成(0初期化なし)は

   bstr = ::SysAllocStringByteLen(NULL, len);

である。このとき、len は生成する領域の byte 数を指定すること。
(終端の NULL はカウントしない)