proxyとstub
EXEサーバの場合、COMはproxyとstubを利用してメソッドを呼び出します。
では、proxyとstubは何をしているのでしょう?
まずは、これらを順に説明してきます。
proxy
proxyはCOMクライアントに対し、coclassが提供するCOMのインターフェースのように振る舞います。
proxyはデータをstubに送り、stubからの返答を待ちます。
stubからの返答を受け取ったら、結果を返します。
例えば、ISequentialStream::Write()のproxyを疑似コードで書くと次のようになります。
HRESULT STDMETHODCALLTYPE CIchigoProxy::Write(
const void* pv, ULONG cb, ULONG* pcbWritten)
{
ULONG id_write = 11; // Write()の識別用ID
HRESULT hr;
// データ送信
send_to_stub( &id_write, sizeof(id_write) );
send_to_stub( &cb, sizeof(ULONG) );
send_to_stub( pv, cb ); // cb[bytes]
// 結果待ち
wait_and_receive_from_stub( &hr, sizeof(hr) );
wait_and_receive_from_stub( pcbWritten, sizeof(ULONG) );
return hr;
}
proxyの処理中にエラーが発生した場合、proxyはエラーに対応したHRESULT値を返します。
したがってproxyが存在する場合、coclassが実装したエラーとは別のHRESULT値が返される可能性があります。
stub
stubはCOMサーバに対し、COMクライアントとして振る舞います。
stubはproxyからのデータ待ちをしていますが、
データを受け取るとCOMのメソッドを呼び出し、その結果を返します。
例えば、ISequentialStreamのstubがデータを受信した際に、
ISequentialStream::Write()を処理するための疑似コードは、次のようになります。
void CIchigoStub_on_receive( ISequentialStream* pobj )
{
ULONG id_method;
HRESULT hr;
wait_and_receive_from_proxy( &id_method, sizeof(id_method) );
switch ( id_method ) {
case 11: // Write()の識別用ID
{
ULONG cb;
ULONG cbWritten;
BYTE* pv;
wait_and_receive_from_proxy( &cb, sizeof(ULONG) );
pv = new BYTE[cb];
wait_and_receive_from_proxy( pv, cb );
hr = pobj->Write( pcb, cb, &cbWritten );
send_to_proxy( &hr, sizeof(hr) );
send_to_proxy( &cbWritten, sizeof(ULONG) );
delete [] pv;
}
break;
... その他のメソッドの処理 ...
}
}
stubの処理中にエラーが発生した場合、
stubはproxyとの通信の切断、coclassの解放といった復帰処理を行うことになるでしょう。
メソッド仲介の方法
先のproxy、stubの例からもわかるように、
メソッドの仲介はインターフェースのメソッドごとに行う必要があります。
COMシステムは、システムに標準で搭載されている多くのCOMインターフェースについて、
メソッドの仲介方法を知っています。
しかし、開発者が独自に定義したcoclassやインターフェースについては、
COMシステムはその仲介方法を知りません。
そこでCOMシステムでは、
次のいずれかに必要な情報がレジストリに登録されていれば、
独自に定義したcoclassやインターフェースであっても、
メソッド呼び出しを仲介する機能を備えています。
インターフェースを保存したバイナリファイル(タイプライブラリ)
インターフェースのポインタを送受信する方法
coclassのポインタを送受信する方法
標準のインターフェースを利用する場合、proxyやstubはシステムに用意されています。
独自に定義したインターフェースを用いる場合でも、多くの場合、
メソッド呼び出しの仲介に必要な情報は自動的に生成できます。
したがって、proxyやstubを自分で実装することはほとんどないでしょう。
メッセージキュー
先に説明したように、proxyやstubはデータ待ちを行い、メソッドを呼び出します。
今まで説明してきたSingle Thread Apartment(STA)の場合、
複数のスレッドにより同時にメソッドが呼び出されることを防ぐために、
coclassのメソッドを呼び出す場合、COMシステムは、
必ずcoclassを作ったスレッド自身から呼び出しを行います。
とはいえ、coclassを作ったスレッド自身が他の処理を実行している間は、
coclassへのメソッドを呼び出すことはできません。
そこで、COMでは、coclassを作ったスレッドが外部からのメソッド呼び出し要求に応えられるときに、
Windowsのメッセージキューが処理されることを仮定しています。
COMのメソッド呼び出しは、ウィンドウの操作などと同様に、
Windowsのメッセージキューにメッセージとして追加され、保留状態になります。
メソッド呼び出しに対応したメッセージがキューから取り出されて、
処理(DispatchMessage)されると、COMシステムのメッセージハンドラが呼び出されます。
この呼び出しはcoclassを作ったスレッド自身で行うため、
このタイミングでproxyやstubを処理することで、
複数のスレッドによるメソッドの同時呼び出しが行われることなく、
COMサーバやCOMクライアントを仲介できることになります。
したがって、Single Thread Apartment(STA)では、Windowsのメッセージキューを処理しなければなりません。
逆に、ウィンドウを作成するなどWindowsのメッセージキューを処理するスレッドは、
Single Thread Apartment(STA)として初期化する必要があります。
関連ページ
COM/ActiveXの解説ページ 目次
前の項目: DLLとEXEの違い
次の項目: サーバ処理の流れ