COM インスタンスを作成する
MEMORANDUM
ATL を用いて COM コンポーネントのインスタンスを生成する方法について調べてみました。
タイプライブラリの組み込み方法と、CoCreateInstance メソッドを用いてインスタンスをスマートポインタに代入する方法について記しています。
COM インスタンスを作成する
■ はじめに
ここの備忘録は、あくまでも走り書き程度のメモ的なものですので気をつけてください。
そのままでもちゃんと動くもの、手を加えないとだめなものなど、未完成レベルのコードです。ので、そのまま引用してしまうなど、プログラムを理解する知識のない方は利用しないでくださいね。
■ 概要
今回は Visual Studio .NET の Visual C++ 7.0 で作成した ComTest という COM コンポーネントを同じく Visual C++ 7.0 のプログラム内で使用するという感じのお話です。VC++ 6.0 とは少しやり方が違ったようなので、まとめて見ました。
ComTest というコンポーネントには、IComTest インターフェイスと、CComTest という CoClass が用意されているものとします。タイプライブラリファイルは _ComTest.tlb です。
■ タイプライブラリを組み込む
VC++ 7.0 で COM を使うために、タイプライブラリを組み込みます。
#import "_ComTest.tlb"
こうすることで、ComTest コンポーネントが持っているインターフェイスや CoClass が有効になります。
ビルド時に、タイプライブラリから _ComTest.tlh と _ComTest.tli という2つのファイルが、作業ディレクトリ (Debug\ など) に展開されるので、具体的な宣言を見たい場合にはそのファイルを参照します。
■ CoCreateInstance()
COM のインスタンスを作成するために、 CoCreateInstance 関数を呼び出します。
HRESULT hr;
CComPtr<ComTest::IComTest> pCT;
hr = CoCreateInstance(__uuidof(ComTest::CComTest), NULL, CLSCTX_ALL, __uuidof(ComTest::IComTest), (void**)&pCT);
if (SUCCEEDED(hr))
{
}
Visual C++ 7.0 で作成した COM コンポーネントの定義は、プロジェクトと同名の名前空間に所属しているようなので、今回の場合は ComTest:: を頭につけます。
これで、スマートポインタ pCT に、IComTest インターフェイスが準備されます。
正常に作成できたかを調べるには、 CoCreateInstance が返した HRESULT の値を、SUCCEEDED(hr) のような感じで判定します。
■ CoInitialize() と CoUninitialize()
通常は意識する必要はないと思いますけど、今回、自分ははまってしまったので書いておくことにしました。
CoInitialize()
CoInitialize() 関数は、CoCreateInstance() を呼び出す前に、1回だけ呼び出す必要があります。
COM を扱うようなプロジェクトの場合は CoInitialize() を呼び出すコードが自動的に用意されるようですけど、Win32 コンソールに ATL をサポートさせたときには、自分で呼び出す必要があるようです。
CoInitialize() を呼び出したら、最後に CoUninitialize() を呼び出す必要があります。
hr = CoInitialize(NULL);
これで CoCreateInstance() を使用する準備が出来ます。これを行わないと、CoCreateInstance() で失敗するので気をつけましょう。
引数は今のところ、必ず NULL を指定しなくてはいけないそうです。
CoUninitialize()
CoInitialize() を呼び出したら、最後にかならず CoUninitialize() を呼び出しましょう。
ここで自分がはまってしまった例を挙げて見ます。細かいエラー処理はここでは省いて見ました。
// 注意:このプログラムは間違いです!!
#import "_ComTest.tlb"
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr;
// スマートポインタ宣言
CComPtr<ComTest::IComTest> pCT;
// COM 系の初期化
hr = CoInitialize(NULL);
// COM インスタンスの作成
hr = CoCreateInstance(__uuidof(ComTest::CComTest), NULL, CLSCTX_ALL, __uuidof(ComTest::IComTest), (void**)&pCT);
// COM 系の初期化
CoUninitialize();
// 終了
return 0;
}
さて、このようにプログラムを組んで実行してみると、メモリ操作まわりでのエラーが発生します。
原因はスマートポインタ (pCT) が解放されるタイミングです。
スマートポインタは参照カウントの調整をシームレスに行ってくれるクラスなのですけど、上のようにそれに頼ったコードの場合、COM インスタンスをリリースしてくれるのはスマートポインタが破棄されるときです。
スマートポインタが破棄されるのは、定義された変数スコープを抜け出すとき。つまり上記の例では _tmain() を終了したときになります。
この時点で既に CoUninitialize() 関数が呼び出されてしまっているためエラーが発生します。
このような場合は、CoUninitialize() が実行される前に明示的に pCT を Release() するか、または CoUninitialize() とスコープをずらすなどの必要があります。
■ スマートポインタの使い方
ATL のスマートポインタは、 CComPtr<ComTest::IComTest> というような感じで宣言すれば、あたかも IComTest のポインタのように振舞ってくれるラッパークラスなのですけど、どうやらこれも Visual C++ 6.0 の頃とは勝手が少し違っているような感じです。
Visual C++ 7.0 で作成した COM をスマートポインタを介してインターフェイスにアクセスすると、[out, retval] 属性のプロパティまたはメソッドの戻り値が、HRESULT ではなく [out, retval] で指定した値になるようです。
ただ、一概そうとはいえないようで、[out, retval] であっても、引数の方で返すという場合もあるようです。その辺りの細かいことがわかったら、また追記することにします。
[out, retval] に指定する型は、たとえば BSTR* といったポインタですが、戻り値に採用されるのは BSTR といったその値のようです。
[out, retval] を指定していないプロパティやメソッドの場合は、そのまま HRESULT の方が採用されるようです。
ただし、どちらの場合も、HRESULT にて失敗が通知された場合には、_com_error という例外が発生します。なので、エラーが発生したかどうかは try / catch ブロックでくくったうえで、 _com_error をハンドルするとった、C++ の例外によるエラー処理が出来るようになっています。
なお、エラーの詳細メッセージは ErrorMessage() 関数か、ISupportErrorInfo のメッセージを取得する Description() 関数で取得します。
HRESULT によるエラー判定をしたい場合は、例外処理にて _com_error をハンドルした上で、WCode メンバ変数を使って判定する必要があります。
このとき、WCode は WORD 型で場合によっては値が変換されていることもあるようなので、HRESULT _com_error::WCodeToHRESULT(WORD) という静的関数によって HRESULT に変換する必要があるようです。