syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

WinSDKおよびATLのCOMユーティリティ

(これは2010-11-23に書いた故OCNブログの記事を移植したものです)

MSXMLなどのCOMタイプ ライブラリを#importしたときなど、COMを扱うときによく見かけるのが、Visual C++ CRTヘルパーの _bstr_t クラスと _variant_t クラス (comutil.h) です。それぞれ、COMのBSTR文字列のラッパーと、VARIANT型のラッパーとなっています。
ただしC++プログラマーとしてはこれらがグローバル名前空間で定義されてやがることに殺意を覚えます。シンボル名がアンダースコアで始まっているのは、処理系依存のデータ型であることを誇示しているのか……
あとアンダースコアなしのエイリアス bstr_tvariant_t がtypedefじゃなく#defineなのも情けないです。これらのエイリアスは使わないほうがよいでしょう。MSの#define好きは異常*1

似たようなラッパーはATL (atlcomcli.h) にも用意されていて、ATL::CComBSTRATL::CComVariantが該当するのですが、ついでにATL::CComSafeArray (atlsafe.h) なんかもあります。MFC専用だとCOleVariantというのもあります。

なお、_variant_tは、MFCライブラリでもたまに使われているのを見かけます(CMFCPropertyGridProperty::SetValue()/GetValue()とか)。

正直VC++ネイティブで文字列を扱う場合、他の環境への移植性を考慮しないで良いのであれば、迷わずCStringA/CStringW/CStringを使うのがベストです*2。そもそもテンプレート機能まである静的型付け言語C++で何が悲しくてVariantなんぞ使わないといけないのかとも思いますが、VBスクリプト言語との相互運用を考慮して設計されたCOMデュアル インターフェイスの境界では、たとえ非効率でもBSTRやVARIANT、SAFEARRAYを使わざるを得ません。で、COMのBSTRは::SysAllocString()/::SysFreeString()、SAFEARRAYは::SafeArrayCreate()/::SafeArrayDestroy()でヒープ管理されるので、こういうRAIIラッパーがないと発狂するでしょう。ただ、ラッパークラスはCRTアロケータを使っていない時点で速度が出ない(とくに文字列の結合が毎回再割り当てを伴うために遅いらしい)し、下手な使い方をするとメモリーリークの原因になるので、COMのRAIIラッパーを使用する場合はCOM境界のみに限定するべきです。ちなみにATL::CComBSTRはアタッチ・デタッチがやや特殊で、使い方を誤ると簡単にメモリーリークするらしいので、極力_bstr_tを使ったほうが無難らしいです。

実装を確認してみたところ、一応 CComBSTR::operator& にはアサーションが入っているので、デバッグビルドであれば誤った使い方をしたときに気付くはずです。

*1:Win32 APIでも、enumを使うべき所なのにやたら#define使っているのがかなりうっとうしいです。あと悪名高いmin/maxマクロも後世に語り継がれる大ポカでしょう。

*2:C++標準のstd::string/wstringより最適化されていて高機能です。Windowsの文字列リソースを直接読み込めるLoadString()メソッドも用意されています。