Dr. GUI と COM イベント:第 1 部

1999 年 8 月 23 日

目次

名医が戻った…
Dr. GUIのビットとバイト
これまでの復習、これからの予定
次号予告:ATLを使ってイベントを簡単に!
これまでの復習、これからの予定

名医が戻った…

雨降りの 6 月も終わり、ようやくシアトルにも夏がやってきました。それにしても、夏時間だと仕事がはかどらないですね。それだけでなく、Tech Ed の準備、Tech Ed に実際に足を運んだこと、Tech Ed の疲れから復帰できなかったこと(http://msevents.microsoft.com/isapi/events/usa/enu/default.asp で Paul Maritz の基調講演記録の 3/4 辺りに書いてある Dr. GUI の活躍を見てください)、そして緊急のプロジェクトに駆り出されたことが手伝って、このコラムを書くのがだいぶ遅くなってしまいました。

再出発

しかし、最も深刻な遅れは、Dr. GUI はこのコラムのデモ プログラム用にマルチ スレッド設計を使いたかったのが原因で起こりました。現在 COM はまったく問題なくスレッドを処理する能力を備えています。Dr. GUI もそうです。しかし、しなければならない作業が大幅に増えます。イベントの基本的な問題に関する議論さえ混乱させてしまうほどの量です。

Dr. GUI は、Windows の SetTimer タイマー API を使うシングル スレッド タイマー オブジェクトがうまく動いていると思っていました。しかし、タイマーに設定されている時間になる前にクライアントがシャット ダウンするといくつかの問題が生じることがわかり、これをうまく解決する方法も見つかりません。そしてこの方法が、メッセージ、タイマー、キューの厄介な問題に絡むことで、イベントの概念の説明を混乱させ始めていました。

そこで代わりに、イベントにできることを見事に示してくれるもっと簡単なオブジェクトを用意しました。このオブジェクトは、冗長なコードの量を減らすために使える強力なプログラミング技術も示してくれます。もちろん、Dr. GUI は将来のコラムで COM のマルチ スレッドの問題に舞い戻るつもりです。

Dr. GUI のビットとバイト

DevDays '99: Windows 2000 でさらに簡単によいアプリケーションを

9 月 15 日にあなたの近くの都市で開催される Dev Days '99 へのお申し込みは今からでも遅くはありません。

Dr. GUI がこれまでも言っているように、Windows 2000 は間もなく登場します。名医は今回こそは間違っていないと思います。1 日の過ごし方として(休みを取るのは除いて)もっともよいのは、Windows 2000 の新機能のすべてを学ぶことでしょう。

Windows 2000 にはアプリケーション(とアプリケーションを実行するシステム)の信頼性を高め、さらに頑丈に、より強力にするための機能がぎっしり詰まっています。これらに加えて、Windows 2000 にはセットアップ、トランザクション、キューなどの複雑なことを行う簡単で標準的な方法を提供する新しいシステム サービスがたくさんあります。

https://msdn.microsoft.com/events/devdays/ で DevDays '99 をチェックしてください。費用は 150 米ドル、ただし PDC での 1 日に比べると安いです。Dr. GUI はお金と時間を有効に使えると確信しています。シアトル在住の方は、名医に会うかもしれませんね(もちろんお忍びで行きますけれど)。

世界最小の Web サーバー

Dr. GUI はどうしてもみんなに知ってもらいたいことがあります。http://www-ccs.cs.umass.edu/~shri/iPic.html で発表されているチップ上に実装された Web サーバーです。クールでしょ。これを見て名医は思わず計算機科学ではなく、電気工学を学べばよかったと思ったくらいです。ちょっとだけですが。

これについては『Seattle Times 』の記事も見ることができます(http://archives.seattletimes.com/cgi-bin/texis.mummy/web/vortex/display?storyID=37b741c61e)。

無料の PC

最近では、誰も彼もが PC をタダで配っているようです。MSN ではそのルートが 2 つもあります。1 つは Micro Center(http://www.microcenter.com)で、もう 1 つは ePCdirect(http://www.epcdirect.com)です。他の会社でも同様のことをしています。http://www.seattletimes.com/news/technology/html98/free_19990718.html の記事をお読みください。

MSDN オンライン「Web サポート サイト トップ 10 入り」

Association of Support Professionals は最近、最優秀「Web サポート サイト(Web Support Site)」を発表しました。Dr. GUI は MSDN がトップ 10 に選ばれてうれしく思います(全体のリストについては、http://asponline.com/awards.html を参照してください)。名医はさらに、MSDN オンラインをより強力にする多くの改良が完成しつつあることをお知らせできて満足です。

Window CE ニュース

ActiveSync 3.0

Windows CE デバイスをお持ちの方は、絶対に ActiveSync 3.0(https://www.microsoft.com/windowsce/products/highlights/activesync.asp)をほしいと思うでしょう。ActiveSync 3.0 は、たいていの Windows CE の同期化ソフトウェア(Windows CE 1.0 搭載の H/PC のオリジナルを除く)の旧バージョンに代わるものです。さらに驚くべきことにきちんと動くのです。

まだ完全とはいえませんが(完全なソフトウェアなんてあるのでしょうか)、前のバージョンよりはるかによくなっているので、アップグレードしないと損ですよ。価格も適切、すなわち無料です。

Windows CE H/PC Pro 用の Terminal Server Client

買い物に出かけて自分用のかっこいい Windows CE H/PC Pro のデバイスを買いましたか?買われた方で Windows NT Terminal Server をお持ちの方は、https://www.microsoft.com/windowsce/products/download/term-serv.asp から Terminal Server クライアントをダウンロードするとよいでしょう。これをインストールすれば(そして適切なライセンスがあれば)、H/PC Pro でほとんどの Windows NT アプリケーションを使うことができます。

H/PC Pro または他の Windows NT Terminal Server クライアント ハードウェアをお持ちでなくても、Windows 95/98/NT マシンをクライアントとして Terminal Server を使ってみてはいかがでしょうか。お勧めする理由は、Terminal Server を使うことにより、お使いのマシンのハードウェア、OS、またはアプリケーション構成と異なるマシンを使えるようになるからです。たとえば、Microsoft の Developer Support グループには、昔の Visual Studio®がセットアップしてある Windows NT Terminal Server があります。顧客が古い開発環境について質問してきたときに、サポート エンジニアは自らのマシンに古いバージョンのアプリケーションをインストールする手間をかけずに、適切な Windows NT Terminal Server マシンに接続して、顧客が直面している問題を再現できます。Visual Studio の古いバージョンで構築されたプロジェクトに対して同じことが行えます。単にプロジェクトと適切なバージョンの Visual Studio(または任意の開発環境)で Windows NT Terminal Server をセットアップするだけです。適切なライセンスを持っていれば、自席でプロジェクトを保守できます。

Windows 2000 DDK

Windows 2000 Device Driver の開発を始めてみませんか。Windows 2000 DDK の RC1(Release Candidate 1) 版は、https://www.microsoft.com/ddk/ から無償でダウンロードできます。

Microsoft Web アプリケーション サーバー—「稲妻のごとく」

Microsoft の Web サーバーにはスケーラビリティがないと思いますか。PC Week のスケーラビリティ ベンチマークの評価はどうやらその逆のようです。Microsoft Web プラットフォームは、「地球上のどんな業務に対しても十分な速さ」だという結論です。

Dr. GUI の読書リスト

名医はここ最近、コンピュータ以外の興味深い読み物を読んでいます。みなさんも、Dr. GUI と同じ本を楽しむだろうという低い可能性に賭けて、題名をいくつか紹介します。

最初に紹介するのは、Andrew Tobias の『The Only Investment Guide You'll Ever Need.』(Dr. GUI は最近個人的な資金繰りを心配しています)。個人の財務についておもしろおかしく書ける人がいるというのは驚きですが、Andrew にはそれができるようです。この本は参考になる上に、実に読んでいて楽しい本です。彼の Web サイトにはデイリー コラムがあり、読みやすくておもしろく、少し突飛だけど参考になります (http://www.andrewtobias.com)。

「このシェーバーが好きだから会社ごと買っちゃった」というシェーバーの広告を知っていますか?Andrew Tobias が売りに出ているというわけではありませんが、彼が書いた本の多くはまだ出版されています。Dr. GUI は全部買って読みました。どれもお勧めの本です。特に彼は Modern Library 版の『The Best Little Boy in the World 』を楽しみました。すべてが明るく、楽しく読めるように書かれていて、どの本も重要で興味深いことが書かれています。Web サイトをチェックしてください(上記参照)。

最後に、Dr. GUI はひそかに長い間待ち望んでいた『Miss Manners' Guide to Excruciatingly Correct Behavior』を取り上げます。実にすばらしい本です。本当はとっても行きたくない招待を礼儀正しく断る方法などを学べます。さらに、名プログラマのような Miss Manners は、なぜ書いたのかその理由についてのコメントも加えているので、礼儀作法以上のものを学べます。

ただし、この本を見つけるのはむずかしいかもしれません。Amazon はこの本を絶版としており、Barnes and Noble は発送までに 2~3 週間かかるとしています。Dr. GUI は幸運にも Costco ですばらしい値段で売られているこの本を見つけました。大きい本屋さんでピンクのカバーを見つけてください。

名医は、Miss Manners を読み終えた後、Matthew Fox の『The Reinvention of Work』に取りかかりました。少しだけコンピュータに関係することも書いておきましょう。たとえば、ATL COM オブジェクトの構築に関する新しい本があります。名医は次回この本について報告できればいいと思っています。

これまでの復習、これからの予定

おそらくお気づきのとおり、COM Automation についての話は終わりました。ついにやった!

3 つのコラムは名医の予測をはるかに超える量となりましたが、Kraig Brockschmidt の本よりは短いと思います(もちろん、彼が当初書こうとしてものよりも短かったです)。「ここでしか見つからない」ポイントをたくさん取り上げているので、少し長くかかっても何の不思議はありません。

今回は、COM イベントについてお話します。ありがたいことに、イベントについてはかなりドキュメント化されています。しかし、どうでもよいというものでもありません。これから説明するように、イベントはときどき十分に活用されていません。したがって、イベントに慣れ親しんでおくのはいいことです。今回は、イベントを発行する方法とイベントを受け取り方についてお話します。

次回は、ATL でイベントを実装する方法とイベントを受け取る Visual Basic を書く方法を説明します。さらに、イベントを使っていかに設計を効率よくできるかについてお話します。

イベント

イベントとは?

ついに、イベントまでたどり着きました。ところで、イベントって何?

思い出してみましょう。今まで COM で見てきた通信はとても一方的なものでした。クライアントはオブジェクトのメソッドを呼び出す。それだけでした。

確かに、オブジェクトは戻り値を返します。しかし、それはクライアントがそのように望んだ場合だけです。まさに「話しかけられたときだけ答えてね」です。

「話しかけられたときだけ答えてね」は、単にコマンドに応えるだけの「単純な」オブジェクトを含めさまざまなオブジェクトで問題なく動作します。

図 1:IFoo インターフェイスを使ったクライアントとそのオブジェクト間の一方向通信。ラベルなしのインターフェイスが IUnknown であることを思い出してください。

しー!クライアント!言わなきゃいけないことがあるんだ ...

しかし、オブジェクトがクライアントに注意を喚起したいことが起きたことを知らせるには、どうすればよいのでしょうか。たとえば、ボタンなど可視のコントロールは自身がクリックされたときにクライアントに通知したいかもしれません。ビジネス ルールを実装するオブジェクトはクライアントに対してルールが破られたことを通知する必要があるかもしれません。あるいは、オブジェクトはバックグラウンドで何かをしていて、それが終了したときにクライアントに通知したいかもしれません(この記事ではバックグラウンドの事例は取り上げません)。

ポーリング

もちろん、オブジェクトに HasButtonBeenClickedHasRuleBeenViolated、または ArentYouDoneYET というようなメソッドを実装することはできます。オブジェクトは自身のステータスを示すフラグを持ち、メソッド呼び出しに対する応答でステータスを返します。しかし、メソッドを継続的にポーリングするのはクライアントの責任です。これは効率が悪く、プログラミングするのも困難です。要するによいことはないのです。

応えてくれるコンポーネント

代わりに、オブジェクトがクライアントのメソッドを呼び出すことができたらどうでしょう。そうしたらオブジェクトは、ボタンがクリックされたこと、ルールが破られたこと、または終了したことなどの条件が満たされたらすぐにメソッドを呼び出せます。そしてクライアントはイベントが発生したことを示す迅速な(必ずしも瞬時でも非同期でもありません)通知を取得します。この仕組みの全体像を示します。

図 2:クライアントからオブジェクトへの通信に IFoo インターフェイスを使い、オブジェクトからクライアントへの通信に IFooEvents インターフェイスを使ったクライアントとオブジェクト間の双方向通信。

ここでクライアントは、オブジェクトが呼び出す対象とするインターフェイスを実装する必要があることに注目してください。ただし、それでもインターフェイスを規定するのはオブジェクトのほうです。オブジェクトはこの外向きのインターフェイスの呼び出し元なので、このインターフェイスはソース インターフェイスと呼ばれています。オブジェクトがイベントの発信源(ソース)と考えれば、覚えやすいと思います。

クライアントは、このインターフェイスに対する呼び出しのシンクとなります。以降、ソースシンクという用語を使ってオブジェクト(ソース)とクライアント(シンク)を表します。名医が「ソース オブジェクト」と呼ぶオブジェクトは、ほかの人たちが「接続可能オブジェクト」と呼ぶものです。これら 2 つはまったく同一のものです。

COM のイベント能力

COM のイベントは基本的に単純です。これらはオブジェクト(ソース)がクライアント(シンク)のメソッドを呼び出すための方法です。これが COM の場合、イベント メソッドはインターフェイス ポインタ経由で呼び出されます。ただし、次の 3 つのことが前提となります。

  1. 同じイベント インターフェイス内に、互いに関連のあるいくつかのイベントがあっても構いません。クリックの種類ごとに(シングルクリック / ダブルクリックなど)、違反ごとに、または処理の進捗段階ごとに(処理中 / 完了)異なるイベントが存在し得ます。

  2. クライアントはインターフェイスを実装する簡単なミニ COM オブジェクトを実装する必要があります(このミニ オブジェクトは一般的に IUnknown とイベント インターフェイスのみを実装します)。クライアントのオブジェクトはソース オブジェクトからの呼び出しを受け取るので、シンク オブジェクトと呼ばれます。

  3. クライアントはソース オブジェクトに対して、どうにかしてシンク オブジェクトのインターフェイス ポインタを渡す必要があります(ここが難しい部分です)。

これらの基本事項に加えて、COM のイベント スキーマは次のいくつかの興味深い機能をサポートします。

  • オブジェクトは、複数のソース(イベント)インターフェイスのソースを提供することができます(これらの個々のインターフェイスは複数のメソッドを持てることを思い出してください。とても柔軟性があります)。

  • 複数のシンク オブジェクトが同じインターフェイスからイベントを受け取れます(これをマルチ キャストといいます)。そのためには、ソース オブジェクトが、イベントを受け取ることに関心を示しているすべてのシンク オブジェクトを記憶しておく必要があります。

  • クライアントのシンク オブジェクトは複数のオブジェクトからイベントを取得できます。

イベントを実装するために必要なもの

ソース インターフェイス

イベント インターフェイスの別名はソース インターフェイスです。インターフェイスに対する呼び出しを行うイベント発行オブジェクトで宣言されているので、ソース インターフェイスと呼ばれています。

このサンプルのソース インターフェイスはとても簡単です。以下に Interface Definition Language(IDL)を示します。

  [
   uuid(F2F660CF-3ED7-11D3-9C8C-000039714C10), 
   helpstring("_IAAAFireLimitEvents Interface")
]
dispinterface _IAAAFireLimitEvents
{
   properties:
   methods:
   [id(1), helpstring("method Changed")]    
            HRESULT Changed(IDispatch *Obj,
                  CURRENCY OldValue);
   [id(2), helpstring("method SignChanged")]    
          HRESULT SignChanged(IDispatch *Obj,
                  CURRENCY OldValue);
};

この Changed イベントはオブジェクトの値が変わるごとに発行され、SignChanged イベントはオブジェクトの値の符号が変わるごとに発行されます。

dispinterface?ウッヘー!

最初に気が付くのが、イベントのインターフェイスの型が恐ろしい dispinterface 型、すなわちディスパッチ インターフェイスである点でしょう。「なぜ?」と疑問に思われることでしょう。Dr. GUI が前回のコラムで、ディスパッチ インターフェイスがだれにとっても効率が悪く、面倒なことを明らかにした後では特にそう感じることでしょう。

ディスパッチ インターフェイスが遅く、プログラミングしにくいという事実があるにも関わらず、1 つだけ長所があります。その長所とは、任意のディスパッチ インターフェイスからの呼び出しを正しく解釈するコードを書くのが比較的簡単だということです。やらなければならないのは IDispatch を実装することです。特に Invoke です。パラメータは variant 型の配列として渡されます。すなわち、パラメータが実際に渡されたときに、パラメータを見つけるためにスタック全体を掘り起こすのよりも解析するのがはるかに簡単ということです。クライアント オブジェクトは、どのようなメソッドが存在するかを(また、必要であれば、メソッドのパラメータも)知るために、タイプ ライブラリを使う必要があります。

もう 1 つ、もっと細かい(しかしより重要な)長所があります。ディスパッチ インターフェイスでは、呼び出しを受け取るオブジェクトはインターフェイス内のすべてのメソッドの実装を提供する必要がないことです。通常のカスタム(またはデュアル)インターフェイスを実装するときは、そのインターフェイスのすべてのメソッドを実装しなければならないこと(少なくとも E_NOTIMPL を返すこと)を思い出してください。

ディスパッチ インターフェイスの場合には、IDispatch のすべてのメソッドを実装する必要があります。しかし、Invoke の実装は、ディスパッチ インターフェイスのすべてのメソッドを実装する必要はありません。この実装は、サポートしないメソッド(つまり、処理することに関心のないイベント)についてはエラー(DISP_E_MEMBERNOTFOUND)を返すことができます。

つまり、Visual Basic や Visual Basic for Applications(VBA)などの言語では、ディスパッチ インターフェイスでイベント呼び出しを受け取るのが、カスタム インターフェイスで受け取るのより簡単なので、これらの言語がサポートするのは、ディスパッチ インターフェイスだけなのです。C++ の場合、独自のシンクを書いていて、他のクライアントのことを気にしないのであれば、パフォーマンスを向上させるためにカスタム インターフェイスが使えます。しかしほとんどの場合、イベントが発生することは比較的まれなので、パフォーマンスが大きな問題になることはほとんどありません。ソース インターフェイスがデュアル インターフェイスであっても意味がありません。デュアル インターフェイスがデュアルであることの意味は、メソッドを直接または IDispatch::Invoke 経由で呼び出せる点にあります。つまり、デュアル インターフェイスは呼び出しを実行する場合ではなく、呼び出しを受ける場合に利用価値があるのです。パフォーマンスがよく、Visual Basic と互換性のあるイベント インターフェイスを持ちたい場合には、異なるインターフェイス ID(IID)を持つ 2 つの同等のソース インターフェイス(1 つはカスタム インターフェイス、もう 1 つはディスパッチ インターフェイス)を実装し、Visual Basic を満足させておくために、ディスパッチ インターフェイスを標準のインターフェイスとします。

もう 1 つ注目してほしいのは、インターフェイスの名前がアンダースコアで始まる点です。これは Visual Basic の命名規則で、インターフェイス名がアンダースコアで始まっている場合、Visual Basic 環境ではそれが表示されません。

これらの点を除けば、IDL コードはこれまでのものとほとんど変わりません。

ソースを使え、ルーク ...

しかしまだ足りないものがります。特定のオブジェクトでインターフェイスがソース インターフェイスだということはどのようにして知ればよいのでしょうか。標準ではインターフェイスは受信インターフェイスなので、これまではインターフェイスに目印を付けることはしませんでした。

インターフェイス自身の IDL には、インターフェイスがソース インターフェイスなのかシンク インターフェイスなのかを示すものは何もありません。使い方によってはいずれにもなり得るのです。ソース インターフェイスなのかどうかはわれわれのオブジェクト次第なので、IDL の coclass 部でそれを指定します。

  coclass AAAFireLimit
{
   [default] interface IAAAFireLimit;
   [default, source] dispinterface _IAAAFireLimitEvents;
};

_IAAAFireLimitEvents インターフェイスが外向きの(ソース)インターフェイスであると同時に標準のソース インターフェイスでもあることを指定していることに注目してください。Visual Basic とスクリプティング言語は COM オブジェクトごとに 1 つのソース インターフェイスしか処理しないので default 属性を使うのが最良の方法です(複数のソース インターフェイスの場合は必須です)。

シンクのインターフェイス ポインタをオブジェクトに渡す方法

インターフェイスが記述できたので、今度はそのインターフェイスへのインターフェイス ポインタを取得しなければなりません(クライアントが正しく実装してくれていると仮定します)。その後、クライアントはソース インターフェイスを実装するシンク オブジェクトを作成して、そのインターフェイス ポインタをソース オブジェクトに渡さなければなりません。

しかし、当然ながら、物事はそれほど簡単にはいきません。

COM は柔軟だけど複雑なソリューション:接続ポイントとコンテナ

COM の設計者はときどき、結局のところあまり使われない余分な機能を追加することがあります。たとえば、いくつかの IDispatch メソッドにインターフェイス ID(IID)パラメータがあったことを覚えているでしょうか。これは、複数のディスパッチ インターフェイスをサポートできるようにすることが目的だったのですが、その機能は実装されることもなく、COM 標準にも取り入れられませんでした。そのため、ディスパッチ インターフェイスは COM オブジェクトにインターフェイスが 1 つだけあるとみなします。

しかし、イベントについては、COM の設計者が上記の機能(オブジェクトごとに複数の外向きインターフェイスなど)をサポートして、さらにオブジェクトのメイン コードを変更せずにイベント インターフェイスを変更できるようにすることを望みました。

この問題に対する COM の解決法はソース オブジェクトに接続ポイントと呼ばれるミニ COM オブジェクトを実装させることです。ソース オブジェクトは外向きのインターフェイスごとに 1 つの接続ポイント オブジェクトを持ちます。各接続ポイントは外向きのインターフェイスを 1 つだけ受け持ちます。

接続ポイントは独立の COM オブジェクトですが、CoCreateInstance にではなくオブジェクトによって作成されます(これについてはすぐ後で説明します)。接続ポイントは通常 2 つのインターフェイス(IUnknownIConnectionPoint)しか実装しません。

接続ポイントと IConnectionPoint

接続ポイントが何をするのかを理解するための最も簡単な方法は、IConnectionPoint インターフェイスのメソッドを見ることです。

もっとも重要なのは AdviseUnadvise です。クライアント(シンク)は接続ポイント オブジェクトの IConnectionPoint::Advise を呼び出して接続を確立します(今はクライアントがどのように接続ポインタ オブジェクトへのインターフェイス ポインタを取得するかについて心配しないでください)。Advise へのパラメータは、イベント インターフェイスを実装するシンク オブジェクトへの IUnknown ポインタです。Advise の実装は接続ポイント オブジェクトにこのインターフェイス ポインタを格納します。その後、ソース オブジェクトは接続ポイント オブジェクトからインターフェイス ポインタを取得して、そのポインタを介してメソッドを呼び出すことによってイベントを発行できます。

Advise はクッキーを返します。クッキーはこの接続を表す一意の値を持つ整数です。接続を切断するには、クライアントが同じクッキーを使って Unadvise を呼び出します。これにより、接続ポイント オブジェクトは関係するインターフェイス ポインタを削除します。クライアントがイベントをこれ以上受け取りたくないときは、このようにすることが重要です。たとえば、クライアントをシャットダウンするときなどです。

マルチキャストがサポートされているので、同じ接続ポイントを使って複数の接続を確立するのは可能です。イベントが発行されると、ソース オブジェクトは接続で正しいメソッドを呼び出されなければなりません。20 個のオブジェクトが Advise を呼び出した後で Changed イベントを発行した場合、Changed に対する 20 個の呼び出しが実行されます。Advise に渡されたインターフェイス ポインタごとに 1 個の呼び出しです(前にも述べたように、接続ポイントはこれらすべてのインターフェイス ポインタを格納する責任を負っています)。

IConnectionPoint の次のメソッドは、どちらかと言えばソース オブジェクトにとって便利なものです。EnumConnectionsIEnumConnections を実装するオブジェクト(これもミニ オブジェクトです)へのポインタを返します。これにより、ソース オブジェクトはメソッドを呼び出すのに必要なインターフェイス ポインタを取得できます(前回の記事で IEnum... インターフェイスについて取り上げたのを覚えているでしょうか)。

最後の 2 つのメソッドはクライアントでもソースでも使えます。これらのメソッドは名前が示すとおりの働きをします。1 つ目のメソッドは、この接続ポイントが持つインターフェイスの IID を返す GetConnectionInterface です。2 つ目のメソッドは、ソース オブジェクトの IConnectionPointContainer ポインタを返す GetConnectionPointContainer です(これについてはいずれお話します)。

復習しておくと、クライアントにとって接続ポイントの機能とは接続を作成(Advise)する手段と切断(Unadvise)する手段を提供する機能のことです。これさえ理解できれば、残りも理解できます。

接続ポイント コンテナと IConnectionPointContainer

これで、接続ポイントさえ取得できれば、接続を確立できる方法を知ることができました。単にシンク ミニ オブジェクトの IUnknown ポインタを接続ポイントの Advise メソッドに渡すだけです。

では、接続ポイントへのポインタはそもそもどのようにして取得すればよいのでしょうか。

COM オブジェクトが複数の接続ポイントをサポートできることはすでに述べました。それには、ソース オブジェクトに IConnectionPointContainer インターフェイスを実装しなければなりません。クライアントは、このインターフェイスを使って接続ポイントを取得できます。

IConnectionPointContainer は、FindConnectionPointEnumConnectionPoints の 2 つのメソッドのみを持つ単純なインターフェイスです。FindConnectionPoint はクライアント オブジェクトによって渡される IID で指定される接続ポイントへのポインタを返します。EnumConnectionPointsIEnumConnectionPoints 列挙子を返します。これを使って、ソース オブジェクトによってサポートされているすべての接続ポイントを順番に調べることができます。

いかに組み合わせるか

さて、ソース オブジェクトが IConnectionPointContainer を実装することはわかりました。このオブジェクトは IConnectionPointContainer のメソッドを使って検索したり、列挙したりできる接続ポイントのコレクションも保持します。各接続ポイントは、接続ポイントの Advise メソッドへの呼び出しによってセットアップされ、接続ポイントの Unadvise メソッドへの呼び出しによって終了するアクティブな接続リストを保持します。これらの接続も列挙できます(接続ポイント オブジェクトに加え、IConnectionPointContainer::EnumConnectionPointIEnumConnectionPoints を実装するオブジェクトと IConnectionPoint::EnumConnectionsIEnumConnections を実装するオブジェクトを作成できる必要があります。すなわち合計 3 つの異なるミニCOMオブジェクトが必要です)。

これは複雑ですが、複数のイベント インターフェイスとマルチキャストをサポートするために必要です。

下図はすべてが接続されたときに全オブジェクトがどのように見えるか示したものです。

図 3:COM の方式で接続ポイントを設定する方法。ICP は IConnectionPoint を表します。Dr. GUI は列挙オブジェクトは省略しました。

イベントをセットアップする呼び出しの順番

これですべてのオブジェクトとインターフェイスがどのようなものかわかったので、クライアントがイベントを受け取るのに必要な手順を導き出せます。

  • クライアントはソース オブジェクトに対して IConnectionPointContainer を問い合わせます。すなわち、すべての接続可能オブジェクト(ソース オブジェクト)は IConnectionPointContainer を実装しなければなりません。オブジェクトは IConnectionPointContainer を実装していないと、イベントをまったく発行しません。

  • QueryInterface が成功すると、クライアントは IConnectionPointContainer::FindConnectionPoint が受け取る必要のあるイベント インターフェイスの IID を渡します。接続可能オブジェクトがそのインターフェイスをサポートする場合、そのインターフェイスの接続ポイント オブジェクトへのポインタを返します。または、クライアントは IConnectionPointContainer::EnumConnectionPoints を呼び出して列挙子を取得し、それを使って、サポートされている接続ポイントをすべて(そして、どれか知っているものがないか)調べることができます。

  • 接続ポイントへのポインタを取得していると仮定して、クライアントはそのポインタの IConnectionPoint::Advise を呼び出し、実際にイベントを受け取るミニ オブジェクトを指す IUnknown ポインタを渡します。クライアントは、Advise によって返されるクッキーを保存しておかなければなりません。

  • この時点で、クライアントはクライアントが渡したインターフェイス ポインタのイベントを受け取ります。

  • イベントを受け取るのをやめるとき(シャットダウンするときなど)は、IConnectionPoint::Unadvise を呼び出し、手順 3 で格納したクッキーを渡します。

イベントの発行

接続を確立すれば、イベントを発行するのは比較的簡単です。ソース オブジェクトは接続を列挙し、それぞれの適切なメソッドを呼び出します。

ほとんどのイベント インターフェイスがディスパッチ インターフェイスなので、適切なメソッドとは IDispatch::Invoke のことです。これに、呼び出し対象となるディスパッチ メソッドを示す正しいパラメータと、必要なパラメータを指定します(ね?これまでのコラムで取り上げたディスパッチ インターフェイスについてのあれこれが役に立つのは分かっていたのです)。

イベントの受信

以後、シンク ミニ オブジェクトはイベントを受け取り、望みのままにそれらを処理します。これは比較的簡単な部分です。

イベントとスレッド

名医は冒頭で、イベントとスレッドに関して問題を抱えていたことをほのめかしました。

まず「特別なこと(あとでお話します)を行うのでない限り、必ず IConnectionPoint::Advise を呼び出したのと同じスレッドからイベントを発行しなければならない」という基本的なルールがあります。別のスレッドを開始して、そのスレッドからイベントを発行することは、普通はしてはいけません。なぜでしょうか。イベントを発行するというのは、インターフェイス ポインタを使って別のオブジェクトへの呼び出しをするということです。

しかし、COM ではスレッド間でインターフェイス ポインタを渡すのは許されていません。代わりにポインタをマーシャリングしなければなりません。これは難しいことではありません。たとえ世界中で 1 番長い名前の API、CoMarshalInterThreadInterfaceInStream を呼び出さなければならいとしてもです(ウェールズの小さな町にちなんで名付けられたのかもしれませんね)。

ストリーム ポインタをもう一方のスレッド(このような特別な場合に動作すると保証されている)に渡し、新しいスレッドの CoGetInterfaceAndReleaseStream を呼び出してマーシャリングされたインターフェイス ポインタを取得します。残念ながら、ATL のイベント発行機能の実装はインターフェイス ポインタをマーシャリングしないので、別のスレッドからイベントを発行するためにいくらかの作業が必要になります。

2 つのよくあるケースでは、これは問題にはなりません。マウス ボタンが押されたなどの Windows メッセージに対応してイベントを発行する場合は問題ないでしょう。作成されたときと同じスレッドにいるときに、メッセージを受け取ることになります。

あるいは、われわれの例ではメソッド呼び出しに対応してイベントを発行しています。この場合も、作成されたときと同じスレッドにいるときに呼び出されるので安全といえます。

新しく作成したスレッドからイベントを発行するのが難しくなるのは、明示的に自分でスレッドを作成した場合(スリープしたり、バックグラウンドで何かを処理する場合など)だけです。

では COM+ イベントはどうなのでしょう

COM+ はイベントの世界に大きな変化をもたらします。もちろん、ここでお話した COM イベントも引き続きサポートされます。しかし COM+ は、パブリッシュ / サブスクライブ スキーマを使った永続的なイベントも含め、イベントを発行しシンクする新しく簡単な方法もサポートします。

これらについてはまだお話しません。今のところ、Visual Basic やスクリプティング言語などが理解できる「古典的な」 COM イベントに焦点をあてています。

次回予告:ATL を使ってイベントを簡単に!

接続ポイントの説明でおわかりのとおり、イベントを発行できるようにするためには次のような多くのことを行わなければなりません。つまり、4 つのオブジェクト(メイン ソース オブジェクト、接続ポイント列挙子、接続ポイント、接続列挙子)の 4 つのインターフェイス(IConnectionPointContainerIEnumConnectionPointsIConnectionPointIEnumConnections)を実装し、2 つのコレクション(接続ポイントと接続)を維持し、イベントを発行したときにすべての接続に対するインターフェイス ポインタの適切なメソッドを呼び出すコードを実装しなければなりません。小さいイベント 1 つ発行するにもこんなにたくさんことをしなければならないのです。

もっと簡単な方法はないのでしょうか。Microsoft Foundation Classes(MFC)を使うと簡単になります。ただし、オブジェクトは大きくなります。または、われわれのヒーローである Active Template Libraries(ATL)を使えば、同じくらい簡単になります。その具体的な方法については、次回にお話します。

これまでの復習、これからの予定

今回は、接続ポイントやその他たくさんのことを含め、COM についてお話してきました。しかし、まだ実際にコードは書いていません。次回にとっておくことにします。コードを説明する記事は次回までおあずけですが、https://msdn.microsoft.com/voices/drgui0899.zip からダウンロードしてコードに目を通すことができます。

それ以後は、永続化について取り上げます。あるいはイベントとスレッドのお話しをするかもしれません。