japan.internet.comThe Internet & IT Network
Twitter
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2009年11月22日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
事業仕分けによる次世代スーパーコンピューターの開発予算削減について、どうお考えですか?
賛成
反対
どちらとも言えない
投票締切 11/30 12:00
デベロッパー2006年7月4日 10:00

COM相互運用機能の利用 - パート2

海外海外internet.com発の記事
  • Post to Twitter
  • Post to Facebook
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!
  • この記事をokyuuへインポート

はじめに

 この記事は、私が書いた記事『COM相互運用機能の利用』の続編です。今回は、.NETイベントをCOMクライアントに公開する方法について説明します。

 パート1の記事と同様、今回もCOMのイベントについての簡単な復習から始めます。これによって.NETイベントをCOMに公開する方法が理解しやすくなります。残念ながら、.NETイベントを作成して後はCOM呼び出し可能ラッパー(CCW)に任せるというような単純なものではありません。COM/.NETのイベント統合を適切に行うには、いくつかの属性を適用する必要があります。

COMイベントの背景

 パート1の記事と同じように、COMイベントの背景についてもインターフェイスを中心に説明します。なぜなら、COMには.NETのような真のイベントのネイティブサポートがないからです。COMはインターフェイスを使ってイベントをシミュレートしています。

 イベントメカニズムには、以下の主要機能が必要です。

  1. 何か(つまりイベント)が発生したことを他のオブジェクトに通知するオブジェクト。通常は、これをソース(送信元)オブジェクトと呼ぶ。
  2. イベントをサブスクライブ(通知待機)するオブジェクト。ソースオブジェクトのイベントを飲み込む(sink)ので、通常は、これをシンクオブジェクトと呼ぶ。
  3. シンクオブジェクトは、ソースオブジェクトと通信してイベントをサブスクライブする。
  4. 1つのソースオブジェクトに対し、複数のシンクによるイベント待機がサポートされる。イベントが発生すると、サブスクライブ中のすべてのシンクにそのイベントが通知される。
  5. シンクオブジェクトは、それ以降のイベント通知が不要になった場合、サブスクライブを解除できる。

 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に組み込まれているさまざまなインターフェイスが、サブスクライブ、サブスクライブ解除、シンクオブジェクトへのコールバックの機能を受け持ち、イベントメカニズム全体を構成しています。

.NETイベントをCOMに公開する

 .NETでは、イベントはデリゲート(委譲)によって実現されますが、ここではデリゲートの説明は省略します。デリゲートの詳細については、MSDNマガジン2003年2月号の記事『A Primer on Creating Type-Safe References to Methods in Visual Basic .NET』を参照してください。

 まず、開発者が特に何もしなかった場合に、.NETイベントがどのようにCOMに公開されるかを見てみましょう。単純なBug(虫)クラスがあり、このクラスがHungryイベント(虫が空腹になると発生するイベント)とFoundイベント(虫が餌、木などを見つけたときに発生するイベント)を公開するとします。

VB.NETの場合
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
C#の場合
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インターフェイスを以下に示します。

VB.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
C#の場合
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インターフェイスを指定します。

VB.NETの場合
<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
C#
[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に公開するイベントの定義はとても簡単ですが、次のような手順が必要です。

  1. 必要なイベントを.NETコンポーネント内で定義する。
  2. .NETコンポーネントのイベントと同じメソッド名とシグネチャを含むインターフェイスを作成する。
  3. InterfaceType属性を使用してこのインターフェイスをIDispatchインターフェイスにする。
  4. .NETオブジェクトでComSourceInterfaces属性を使用して、このインターフェイスをCOMイベントのソースインターフェイスにする。

著者紹介

Patrick Steele(Patrick Steele)
ミシガン南東部で独立コンサルタントとして活動。ASP.NET、WinForms、COM+、COM相互運用機能など.NETの広範な経験を持つ。ミシガン五大湖地域の.NETユーザーグループ(GANG - http://www.migang.org)の主事でもあり、過去5年にわたりMicrosoft社の.NET MVPを受賞。連絡したい場合は、ブログhttp://weblogs.asp.net/psteeleから、またはpatrick@mvps.orgまで。
関連テーマ
  • プリンター用
  • 記事を転送
  • Post to Twitter
  • Post to Facebook
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • BuzzurlにブックマークBuzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!
  • この記事をokyuuへインポート
最新トップニュース
Graphic Design Forum
【Graphic Design Forum】
流動的媒体と静的媒体に関する見解(11月18日)
「IT の耳」
「IT の耳」
【書評】『Hyper-V スタートアップバイブル』――仮想化についてのすぐれた解説書(11月20日)
百式のネットビジネス研究
百式のネットビジネス研究
世界でもっともパワフルな iPod のスピーカー「Wall of Sound」(11月20日)
週刊-サイト別アクセス状況データ
週刊-サイト別アクセス状況データ
ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(11月19日)
海外ソーシャルウェブに学ぶ成功の秘訣
海外ソーシャルウェブに学ぶ成功の秘訣
ゲーム業界を襲う世界的な激震。ソーシャルゲーム急成長のインパクト(11月19日)
今さら聞けない初歩からのアクセス解析
今さら聞けない初歩からのアクセス解析
サイトリニューアル前のアクセス解析活用法(11月19日)
成約率、反応率を上げる Web 文章術
成約率、反応率を上げる Web 文章術
文章力を磨き、キャッシュを生み出す Web サイト に(11月19日)
「Webからの脅威」―その傾向と最新対策
「Webからの脅威」―その傾向と最新対策
新たな対策技術:スパムフィルタリングと E-mail レピュテーション(11月18日)
ROI向上のための戦略的WebPR
ROI向上のための戦略的WebPR
「戦略的 WebPR」のしかけ方〜WebPR の効果測定手法とは〜(11月18日)
スマートにソーシャルウェブを構築しよう
スマートにソーシャルウェブを構築しよう
社員力を生かすソーシャルメディアポリシー(11月17日)
DevX
DevX
Erlangを使った並列処理プログラムの作成(11月17日)
Copyright 2009 Japan Internet.com K.K. All Rights Reserved.http://www.internet.com/