.NETイベントをCOMに公開する
.NETでは、イベントはデリゲート(委譲)によって実現されますが、ここではデリゲートの説明は省略します。デリゲートの詳細については、MSDNマガジン2003年2月号の記事『A Primer on Creating Type-Safe References to Methods in Visual Basic .NET』を参照してください。
まず、開発者が特に何もしなかった場合に、.NETイベントがどのようにCOMに公開されるかを見てみましょう。単純なBug(虫)クラスがあり、このクラスがHungryイベント(虫が空腹になると発生するイベント)とFoundイベント(虫が餌、木などを見つけたときに発生するイベント)を公開するとします。
Option Strict On Option Explicit On Namespace BugVB Public Class Bug Public Delegate Sub HungryEventHandler() Public Delegate Sub FoundEventHandler(ByVal item As String) Public Event Hungry As HungryEventHandler Public Event Found As FoundEventHandler End Class End Namespace
using System; namespace BugCS { public class Bug { public delegate void HungryEventHandler(); public delegate void FoundEventHandler(string item); public event HungryEventHandler Hungry; public event FoundEventHandler Found; } }
VB.NETのコード例で、イベントのデリゲートを明示的に定義している点に注目してください。この定義はVB.NETで自動的に行われるものなので必須ではありませんが、同じ処理のコードをC#とVB.NETの両方で表す場合、私はコードができる限り同じになるようにし、便利なコンパイラ機能に依存しないようにしています。
TLBEXP.EXEを使用してこのオブジェクトのCOMタイプライブラリを生成すると、タイプライブラリには各デリゲートのコクラスができますが、イベントを検出するメカニズムは含まれていません。前述のVB6の例のようなソースインターフェイスがないのです。ソースインターフェイスがないと、VB6でこのタイプライブラリへの参照を追加してオブジェクトブラウザで確認しても、イベントは表示されません。

COMオブジェクトがCOMのコネクションポイントプロトコルを使ってこれらのイベントをサブスクライブできるようにするには、HungryメソッドとFoundメソッドを定義するインターフェイスが必要です。このインターフェイスによって、シンクオブジェクトへのコールバックが行えるようになります。スムーズな.NET/COM統合を行うために、この手順に進みましょう。
イベントを表すインターフェイスの定義
まず、通常の.NETインターフェイスを定義することから始めましょう。それからそのインターフェイスをCOMソースインターフェイス(イベント用インターフェイス)として公開します。ここで以下の点が重要になります。
- 他のクラスやインターフェイスをCOMに公開する場合と同様、固有のGUIDが必要です(COM相互運用機能とGUIDの詳細についてはパート1の記事を参照)。
- これらのイベントがVB6で動作するようにするには、イベントをCOMの
DispInterface型にします。これを行うにはインターフェイスにInterfaceType属性を適用します。 - ソースインターフェイスの各メソッドのメソッド名およびシグネチャを、各イベント(デリゲートではない)の名前およびシグネチャと完全に同一にします。
- ソースインターフェイスの各メソッドには、0より大きい一意の値の
System.Runtime.InteropServices.DispId属性を適用します。一意のDispIdを指定すると、COMは遅延バインディングを行わずに直接メソッドを呼び出せます。
Bugオブジェクトのソースインターフェイスとして使用する.NETインターフェイスを以下に示します。
Imports System.Runtime.InteropServices <Guid("A66356CF-7408-4bf5-B02E-17158FE30DA3"), _ InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _ Public Interface IBugEvents <DispId(1)> _ Sub Hungry() <DispId(2)> _ Sub Found(ByVal item As String) End Interface
using System.Runtime.InteropServices; [Guid("A66356CF-7408-4bf5-B02E-17158FE30DA3")] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IBugEvents { [DispId(1)] void Hungry(); [DispId(2)] void Found(string item); }
インターフェイスを定義したので、これをイベントのソースインターフェイスにすることをTLBEXP.EXEに指定します。Bugクラスでこのインターフェイスを実装する必要はありません。このインターフェイスはCOM相互運用機能がプレースホルダとして使うだけです。インターフェイスメソッドのシグネチャを.NETイベントのシグネチャと同じにすることが重要なのはそのためです。TLBEXP.EXEにとってはプレースホルダにすぎないので、これらが同じでなくても.NETではその不一致が検出されないからです。BugクラスではComSourceInterfaces属性を使用して、どのインターフェイスをイベントシンクが受信できるソースインターフェイスとするかをCOMに通知します。Bugクラスの例では、IBugEventsインターフェイスを指定します。
<ComSourceInterfaces(GetType(IBugEvents))> _ Public Class Bug Public Delegate Sub HungryEventHandler() Public Delegate Sub FoundEventHandler(ByVal item As String) Public Event Hungry As HungryEventHandler Public Event Found As FoundEventHandler End Class
[ComSourceInterfaces(typeof(IBugEvents))] public class Bug { public delegate void HungryEventHandler(); public delegate void FoundEventHandler(string item); public event HungryEventHandler Hungry; public event FoundEventHandler Found; }
この.NETクラスをCOMにエクスポートすると次のようになります。
coclass Bug {
[default] interface _Bug;
interface _Object;
[default, source] dispinterface IBugEvents;
};
パーフェクトです。これでIBugEventsインターフェイスがソースインターフェイスとして指定されました。「COM相互運用機能の利用」パート1の記事と同様、ここでも周辺の具体的な処理は省略しました。このサンプルでは、COMのイベントメカニズムを示す部分だけを記載してます。VB6のオブジェクトブラウザでこのタイプライブラリを確認すると、意図したとおりにイベントが表示されます。

まとめ
COMに公開するイベントの定義はとても簡単ですが、次のような手順が必要です。
- 必要なイベントを.NETコンポーネント内で定義する。
- .NETコンポーネントのイベントと同じメソッド名とシグネチャを含むインターフェイスを作成する。
InterfaceType属性を使用してこのインターフェイスをIDispatchインターフェイスにする。.NETオブジェクトでComSourceInterfaces属性を使用して、このインターフェイスをCOMイベントのソースインターフェイスにする。
