はじめに
この記事は、私が書いた記事『COM相互運用機能の利用』の続編です。今回は、.NETイベントをCOMクライアントに公開する方法について説明します。
パート1の記事と同様、今回もCOMのイベントについての簡単な復習から始めます。これによって.NETイベントをCOMに公開する方法が理解しやすくなります。残念ながら、.NETイベントを作成して後はCOM呼び出し可能ラッパー(CCW)に任せるというような単純なものではありません。COM/.NETのイベント統合を適切に行うには、いくつかの属性を適用する必要があります。
COMイベントの背景
パート1の記事と同じように、COMイベントの背景についてもインターフェイスを中心に説明します。なぜなら、COMには.NETのような真のイベントのネイティブサポートがないからです。COMはインターフェイスを使ってイベントをシミュレートしています。
イベントメカニズムには、以下の主要機能が必要です。
- 何か(つまりイベント)が発生したことを他のオブジェクトに通知するオブジェクト。通常は、これをソース(送信元)オブジェクトと呼ぶ。
- イベントをサブスクライブ(通知待機)するオブジェクト。ソースオブジェクトのイベントを飲み込む(sink)ので、通常は、これをシンクオブジェクトと呼ぶ。
- シンクオブジェクトは、ソースオブジェクトと通信してイベントをサブスクライブする。
- 1つのソースオブジェクトに対し、複数のシンクによるイベント待機がサポートされる。イベントが発生すると、サブスクライブ中のすべてのシンクにそのイベントが通知される。
- シンクオブジェクトは、それ以降のイベント通知が不要になった場合、サブスクライブを解除できる。
COMでは、「コネクションポイントプロトコル」というプロセスでインターフェイスを介してこれらのメカニズムを実現しています。
ソースオブジェクト
COMのコネクションポイントプロトコルでは、あるオブジェクトをイベントのソースにするには、そのイベントを表現するメソッドを含んだインターフェイスをソースオブジェクト内に定義します。この点は重要なので、確実に覚えておいてください。
例えばClick
イベントとMouseOver
イベントを持つ単純なVB6オブジェクトがあるとします。このオブジェクトの名前を「CButton」としましょう。コンパイルすると、このオブジェクトには既に1つのインターフェイスが実装されています。パート1の記事でも説明したデフォルトインターフェイスです。このデフォルトインターフェイスはVB6によって自動的に作成され、この場合は「_CButton」という名前になります。ここではもう1つ「__CButton」という名前のインターフェイスも作成されます。CButton
が公開するイベントはこのインターフェイスで定義されます。
VB6コードは次のようになります。
Public Event Click() Public Event MouseOver(ByVal x As Integer, ByVal y As Integer)
これをコンパイルすると、COMタイプライブラリで示されるCButton
コクラスは次のようになります。
coclass CButton {
[default] interface _CButton;
[default, source] dispinterface __CButton;
};
1番目のインターフェイス_CButton
は、VB6で作成されたデフォルトインターフェイスです。2番目のインターフェイス__CButton
は、デフォルトのソースインターフェイス、つまりイベントです。この2番目のインターフェイスも、VB6によって自動的に作成され、タイプライブラリで次のように定義されます。
dispinterface __CButton { properties: methods: [id(0x00000001)] void Click(); [id(0x00000002)] void MouseOver( [in] short x, [in] short y); };
VB6で定義したイベントが、ソースインターフェイスのメソッドになっている点に注目してください。この点を理解しておくことが、COMに公開する.NETイベントを設定する上で重要です。
このソースオブジェクト(CButton
)では、IConnectionPoint
という特別なインターフェイスも実装されます(タイプライブラリでは見えません)。このCOMインターフェイスがあることですべてのソースインターフェイスのリストを取得できるので、他のオブジェクトはイベントのサブスクライブやサブスクライブ解除、およびイベントリストの取得を行えます。VB6 IDEも、IConnectionPoint
インターフェイスを使うことですべてのソースインターフェイスを列挙でき、これによって各オブジェクトがどのようなイベントをサポートしているかを認識できます。
シンクオブジェクト
イベント発生の通知を受ける、つまりイベントを「サブスクライブ」する側のオブジェクトは、ソースインターフェイスを実装することでこれが可能になります。上記の例の場合は、シンクオブジェクトに__CButton
インターフェイスを実装し、Click
メソッドとMouseOver
メソッドにイベント処理コードを追加します。シンクオブジェクトは、IConnectionPoint
インターフェイスを介して自身のオブジェクトインスタンスをソースオブジェクトに渡します。ソースオブジェクトは、このインスタンスの参照を、イベントを待機するシンクオブジェクト(サブスクライバ)のリストに追加します。
ソースオブジェクトがイベントを発生させるときには、サブスクライバリストを順に見ていき、個々のサブスクライバをインターフェイスにキャストして、各シンクオブジェクトの当該イベントを表すインターフェイスメソッドを呼び出します(先ほど述べたように、シンクオブジェクトにはイベントを表すインターフェイスが実装されています)。例えばClick
イベントの場合は、このイベントのサブスクライバリストに含まれるすべてのシンクオブジェクトのClick
メソッドが呼び出されます。シンクオブジェクトには__CButton
インターフェイスが実装されているので、Click
メソッドのコードを実行できます。
このように、COMのコネクションポイントプロトコルは複雑なコールバックの仕組みになっています。コールバック(イベント)はインターフェイスで定義されます。COMに組み込まれているさまざまなインターフェイスが、サブスクライブ、サブスクライブ解除、シンクオブジェクトへのコールバックの機能を受け持ち、イベントメカニズム全体を構成しています。