none
COM相互運用とイベントについて RRS feed

  • 質問

  • はじめまして。

     

    .NET2005の環境で、C#によるクラスライブラリと、C++によるアンマネージドなCOMを作成しています。

    ここで、C#をイベントソースとし、COMをイベントシンクとして、イベントの通知を行いたいのですが、

    うまくゆきません。(C#からCOMの呼び出しはうまくいっています。)

     

    以下を参考としました。

    [C#イベントソース]

    http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/cpguide/html/cpconraisingeventshandledbycomsink.asp

    COM シンクによって処理されるイベントの発生

     

    [COM]

    http://www.s34.co.jp/cpptechdoc/misc/comevent/

    COMからのイベントを捕まえる方法

     

    C#のソース側でイベントClick()を呼び出すのですが、

      System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。

    のエラーとなってしまいます。

    イベントClickがNULLとなっているためであると思います。

     

    イベント通知時には一般的には、”+=”を用いてイベントハンドラの登録を行いますが、

    この場合もやはり必要なのでしょうか。

    クライアント(イベントシンク)側で実装しAdviseしたインターフェースとは

    どのようにすれば結びつけられるのでしょうか。

     

    いろいろ探したのですが、上記以上に具体的なサンプルは見つけられませんでした。

    アドバイスをよろしくお願いします。

    2008年2月12日 12:37

回答

  • yayadon様

    アドバイスありがとうございます。

    おかげさまで動作させることができました。

     

    いただいたサンプルと、こちらの実装の差異について見てみましたが、

     

     1)クラスをHogeClassとHogeClassEvにわけない。(VBからの呼び出しに余計なNew?)

     2)Formクラスの中で、別途Newしたためインスタンスが2重化してしまった。

     

    あたりが要因のように思います。

     

     3)protected virtual void OnHogeActionを使用する。

     

    これは直接関係ないようですね。以前WEBをあさっているときにイベントにおける

    OnXXXXXXの働きについて見たことがあったので、ひょっとしてと思いましたが。

     

    詳細については今後の課題です。

     

    VBでの動作を確認後、C++COMにおいてもイベント受信を確認できました。

    ありがとうございました。

    2008年2月18日 10:18

すべての返信

  • 状況がつかめない所がありますので確認させてください。

    C#とC++、どちらがサーバーでどちらがクライアントなのでしょうか?

    「C#からCOMの呼び出しはうまくいっている」から想像するとC#がクライアントとも思いますが

    「C#をイベントソースとし、COMをイベントシンクとして、イベントの通知を行いたいのですが」から想像すると

    C++のCOMがクライアントですよね?

    両方を満たすとどちらもサーバーであり、クライントという状況になりますが、そうしてしまうと相互参照になり解放できなくなってしまいます。

     

    それとも以下のような構成でしょうか?

    C#(クライアント)

     ↓

    C++(COM)

     ↓

    C#クラスライブラリ(COMインターフェイス実装)

     

     

    2008年2月12日 13:27
  • C.Johnさん返信ありがとうございます。

     

    C#がサーバーで、C++がクライアントです。
    以下のような構成です。

     

           C++(COMクライアント)
               ↓    ↑
    関数呼び出し↓    ↑イベントコールバック
               ↓    ↑
              C#(サーバー)

     

    クライアントとサーバーが互いに関数呼び出しを行うと、相互参照となり解放できなくなることは
    わかります。(やってみました 笑)

    それを防ぐためのコールバックであると認識しているのですが、間違っていますでしょうか。

    2008年2月13日 0:55
  •  

    たぶん,うっかりミス or タイプライブラリでの値の確認の間違い 等で,
    SINK_ENTRY_EX マクロ の 3番目の引数で指定する DISPID の値が違っているんじゃないですかね。

     

    例えば,

    リンク先の C# での Managed COM Server での インターフェイスの定義のところでは,

    DISPID を指定していないので,

    0x60020000 や 0x60020001 ... などの値に勝手になってしまっています。

     

    その値を DispIdAttribute属性 で指定しておくと楽になります。(というより,指定しておかないとのちのち具合が悪いかも)

    Code Snippet

     

        // Step 1: Defines an event sink interface (ButtonEvents) to be    

        // implemented by the COM sink.

        [GuidAttribute("1A585C4D-3371-48dc-AF8A-AFFECC1B0967")]

        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]

        public interface ButtonEvents

        {

     

            [DispId(0)]

            void Click(int x, int y);

            [DispId(1)]

            void Resize();

            [DispId(2)]

            void Pulse();

        }

     

     

    # まったく関係ないけど,

    # リンク先で SINKID_COUNTEREVENTS という命名があるけど,

    # イベント・ソース・オブジェクト としての dispinterface を識別するためのものなので,

    # sink側 でなく source側 を識別する ID として(任意の値を)定義するのだから,

    # COUNTEREVENTS_SOURCE_ID や SOURCE_ID_COUNTEREVENTS などという

    # source という命名の方が無難な気がするんですけどね。

     

    2008年2月13日 8:40
  • yayadonさん。こんにちは。

     

    実際のプログラムではDispId指定も行っていました。

    DispId(0) としたとき、TLBのID値が0となっていることは確認しています。

    (1とか5とか変えてもみましたが...)

     

     

    ># イベント・ソース・オブジェクト としての dispinterface を識別するためのものなので,

     

    ついでの逆質問で申し訳ありませんんが、この識別値は「N番目のdispinterface」ということを

    あらわしているということなのでしょうか。ソース側には対応する識別定義がないようなので。

     

    2008年2月14日 0:18
  • この識別値は「N番目のdispinterface」ということを

    あらわしているということなのでしょうか。

     

    イベントソースは,複数持てるので,

    SINK_ENTRY_EX マクロで,

    その複数の IDispatchインターフェイスを単に区別するために,

    シンク側で勝手に番号を打ってるだけです。

     

    ひとつのSink用のクラス(イベント・レシーバ&ハンドラとしてのクラス)に対して,

    ひとつの イベントソース つまり IDispEventImpl<略> を継承しているだけなら,

    いいけど,

    実際は,ひとつのSink用のクラスで,複数の IDispEventImpl<略> を指定可能なので,

    (ひとつのクラスでまとめてハンドリングが可能になるので便利になる場合があるということ)

    そのSink用のクラスの中で,

    BIGIN_SINK_MAP 内の SINK_ENTRY_EX の一番目の引数が,

    なんとかSINK_ID や SINKID_なんとか となるのは意味的にややこしいんじゃないかと意味です。

    それ以上の深い意味はないです。すみません。余計なコメントでした。

    # 命名はどうでもいいので今回の動かない件とはまず関係ないです。

     

     

    リンク先のコードとは別物ですが,

    C# などで作成したいわゆる 「マネジド COM サーバー」 からのイベントを

    ATL で作成したCOMオブジェクトで受け取れることは確認はしています。

    マネジドだからやれないということはないと思います。

     

    ただ,IDispEventImpl<> は,

    実行時に タイプライブラリからイベントのメソッドの情報

    (下の _ATL_FUNC_INFO 構造体 の作成を実行時にやってくれる) を取得するので,

    なんらかの事情で,

    タイプライブラリをレジストリ(HKCR\TypeLib\下)に登録できない/されていない場合などでは,

    IDispEventSimpleImpl<> と _ATL_FUNC_INFO 構造体を使ってやらないといけません。

    http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=2546615&SiteID=7

    で紹介した ATL Internal 2nd Ed. の

    457ページから 468ページまでがイベントシンクのつくり方,

    468ページから488ページまではカラクリ(生C++での)が書いてあります。

    (追記: この場合は,エラーになるだろうから今回の件とはたぶん関係ないですね。)

     

    ただ,「マネジド COM サーバー」側に原因がある場合もありますよね?

    なので,

    すでにやってるかもしれませんが,

    まず,COMと一番相性のいい VB6.0 もしくは Excel VBA などで

    C# などで作成したいわゆる 「マネジド COM サーバー」 からのイベントを

    受け取れることを確認してみるのはどうでしょう?

    2008年2月14日 5:04
  • 一コマ目の 

    。(C#からCOMの呼び出しはうまくいっています。)
     

    は,

    COM から C# への呼び出しはうまくいっています。

    の書き間違いですよね?

     

     

     

     

     

    2008年2月14日 5:43
  • yayadonさん。詳細な返信ありがとうございます。

     

    シンク側で勝手に番号を打ってるだけです。

    SINK_ENTRY_EX マクロ側の都合ということですね。了解しました。

     

    >まず,COMと一番相性のいい VB6.0 もしくは Excel VBA などで

    >C# などで作成したいわゆる 「マネジド COM サーバー」 からのイベントを

    >受け取れることを確認してみるのはどうでしょう?

     

    C++にこだわっていたので、目からウロコです。

    そうすれば切り分けができるかもしれないですね。

    試してみます。ありがとうございました。

    2008年2月14日 8:45
  • >COM から C# への呼び出しはうまくいっています。

    >の書き間違いですよね?

     

    お恥ずかしい。

    その後に書いていることと矛盾してますね。

    ご指摘の通りです。

    2008年2月14日 8:47
  •  

    WEB上の他のサンプルだと,(WTL を利用したものですが)

    先のリンク先と同じ IDispEventImpl<> を利用した例が

    イベントシンクのカプセル化 - IDispEventImpl

     

    また,

    IDispEventSimpleImpl<> を利用した例が

    イベントシンクのカプセル化 - IDispEventSimpleImpl

    にありますね。 

     

    ただWTLのクラス内にラップしている形になっているので,

    WTL のことを知らないとかえって参考にならないかもしれませんが。

     

    イベント通知時には一般的には、”+=”を用いてイベントハンドラの登録を行いますが、

    この場合もやはり必要なのでしょうか。

     

    上の例なら,

    イベント・ソース・オブジェクト となるブラウザコントロールのIUnknownポインタ (m_pWB2) に対して

    Code Snippet

      DispEventAdvise(m_pWB2, &DIID_DWebBrowserEvents2);

     

    しているところです。(ATLの AtlAdvise 関数を呼んでます)

     

     

     

    2008年2月14日 18:10
  • お世話になります。

     

    その後、yayadonさんのアドバイスに従ってクライアント側をExcelVBAとしてみました。
    相変わらず、ソース側のイベントがNULLのままとなっています。

    ほぼ、リンク先のサンプルのままなのですが。

     

    以下のような動作となっています。

     1)VBA シート上のボタン押下で、サーバーHogeClassのOpenFormを呼び出す。
     2)サーバーはフォームを表示するので、そのボタンを押下する。
     3)サーバーのボタン押下処理button1_Click()で、クラスHogeClassEvを生成し、CauseHogeActionを呼ぶ。
     4)サーバーのHogeClassEvクラス:CauseHogeActionにおいて、イベントHogeActionを呼ぶ。

     

    このHogeActionが、NULLのままなのでエラーとなります。

     

    Code Snippet

    //*********** Class1.cs ***********

        public delegate void HogeActionEventHandler(string item);

     

    //イベントインターフェース
        [GuidAttribute("83ACF773-1A40-47fc-B87B-666370FE0434")]
        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IHogeEvents {
                [DispId(1)]
                void HogeAction(string item);
        }

    //イベント処理クラス
        [GuidAttribute("B0AAD471-7CC0-47fc-9D3F-A16884D8D2DC")]
        [ComSourceInterfaces(typeof(IHogeEvents))]
        public class HogeClassEv {

     

            public event HogeActionEventHandler HogeAction;

     

            public HogeClassEv() {}
            public void CauseHogeAction(string item)  {
                HogeAction(item);
            }
        }

    //受付インターフェース
        public interface IHogeClass  {
            void OpenForm();
        }

    //受付クラス
        [ClassInterface(ClassInterfaceType.None)]
        public class HogeClass : IHogeClass  {
            public HogeClass()  {}

            public void OpenForm()  {
                Form f = new Form1();
                f.Show();
            }
        }

    //*********** Form1.cs ***************
    //フォームクラス
        public partial class Form1 : Form  {
            public Form1()  {
                InitializeComponent();
            }
            private void button1_Click(object sender, EventArgs e) {
                HogeClassEv ce = new HogeClassEv();
                ce.CauseHogeAction("Call Event");
            }
        }

    //*********** VB - sheet1コード ***********

    Public WithEvents mym As HogeClassEv

    Private Sub Class_Initialize()
        Dim o As Object
        Set o = New HogeClassEv
        Set mym = o
    End Sub

    //イベント処理
    Private Sub mym_HogeAction(ByVal s As String)
        MsgBox "mym_HogeAction"
    End Sub

    //ボタン押下処理
    Private Sub CommandButton1_Click()
        Dim abc As Object
        Set abc = New HogeClass
        abc.OpenForm
    End Sub

     

     

    以前リンク参照した「COMシンクによって処理されるイベントの発生」のサンプルと異なるのは、
      サンプル    [ComSourceInterfaces(GetType(IHogeEvents))]
      上記実装     [ComSourceInterfaces(typeof(IHogeEvents))]
    ですが、(GetTypeがコンパイルエラーとなってしまう)
    TLBを見ると、
          [default, source] dispinterface IHogeEvents;
    となっているので、問題はないと思っています。

     

    何かアドバイスまたはご指摘をいただければ幸いです。
    よろしくお願いします。

     

    yayadonさん

    ご返信ありがとうございます。

     

    なるほど、シンク側のAdviseが、ソース側での”+=”に相当するわけですね。

    了解しました。

    けど WTLはまだ荷が重いです。

     

     

     

     

     

     

     

     

    2008年2月16日 5:08
  • 上の例なら,
    Managed COMサーバー側は,例えば,以下のような感じにします。

     

    Code Snippet

     

    using System;

    using System.Runtime.InteropServices;

    using System.Windows.Forms;

     

    namespace MyManagedComServer

    {

        public delegate void HogeActionEventHandler(string item);

     

        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]

        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

        public interface _IHogeEvents

        {

            [DispId(1)]

            void HogeAction(string item);

        }

     

        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]

        public interface _IHogeClass

        {

            void OpenForm();

        }

     

        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]

        [ComSourceInterfaces(typeof(_IHogeEvents))]

        [ClassInterface(ClassInterfaceType.None)]

        public class HogeClass : _IHogeClass, IDisposable

        {

            Form1 _frm;

     

            public HogeClass() { }

     

            public void OpenForm()

            {

                _frm = new Form1(this);

                _frm.Show();

            }

     

            public event HogeActionEventHandler HogeAction;

     

            protected virtual void OnHogeAction(string item)

            {

                HogeActionEventHandler tmp = HogeAction;  // スレッド絡みのおまじない

                if (tmp != null)

                {

                    tmp(item);

                }

            }

     

            public void RaiseHogeAction(string item)

            {

                OnHogeAction(item);

            }

     

            #region IDisposable Members

     

            public void Dispose()

            {

                if (_frm != null)

                {

                    _frm.Dispose();

                }

            }

     

            #endregion

        }

     

    }

     

     

     

    Code Snippet

     

    using System;

    using System.Windows.Forms;

     

    namespace MyManagedComServer

    {

        public partial class Form1 : Form

        {

            MyManagedComServer.HogeClass _owner;

     

            public Form1()

            {

                InitializeComponent();

            }

     

            public Form1(MyManagedComServer.HogeClass owner)

                : this()

            {

                _owner = owner;

            }

     

            private void button1_Click(object sender, EventArgs e)

            {

                if (_owner != null)

                {

                    _owner.RaiseHogeAction("Call Event");

                }

            }

        }

    }

     

     

     

    で,クライアント側は,

    MyManagedComServer.HogeClass のみを扱う感じです。

     

    以下は,Access のフォームです。

     

    Code Snippet

     

    Option Explicit

     

    Dim WithEvents m_action As MyManagedComServer.HogeClass

     

    Private Sub Form_Load()

        Dim o As Object

        Set o = New MyManagedComServer.HogeClass

        Set m_action = o

    End Sub

     

    Private Sub Form_Unload(Cancel As Integer)

        Set m_action = Nothing

    End Sub

     

    Private Sub m_action_HogeAction(ByVal item As String)

        MsgBox "呼ばれました。" & item

    End Sub

     

    Private Sub コマンド0_Click()

        m_action.OpenForm

    End Sub

     

     

     

     

     

     

     

     

     

    2008年2月16日 9:57
  • yayadon様

    アドバイスありがとうございます。

    おかげさまで動作させることができました。

     

    いただいたサンプルと、こちらの実装の差異について見てみましたが、

     

     1)クラスをHogeClassとHogeClassEvにわけない。(VBからの呼び出しに余計なNew?)

     2)Formクラスの中で、別途Newしたためインスタンスが2重化してしまった。

     

    あたりが要因のように思います。

     

     3)protected virtual void OnHogeActionを使用する。

     

    これは直接関係ないようですね。以前WEBをあさっているときにイベントにおける

    OnXXXXXXの働きについて見たことがあったので、ひょっとしてと思いましたが。

     

    詳細については今後の課題です。

     

    VBでの動作を確認後、C++COMにおいてもイベント受信を確認できました。

    ありがとうございました。

    2008年2月18日 10:18