トップ回答者
COM相互運用とイベントについて

質問
-
はじめまして。
.NET2005の環境で、C#によるクラスライブラリと、C++によるアンマネージドなCOMを作成しています。
ここで、C#をイベントソースとし、COMをイベントシンクとして、イベントの通知を行いたいのですが、
うまくゆきません。(C#からCOMの呼び出しはうまくいっています。)
以下を参考としました。
[C#イベントソース]
COM シンクによって処理されるイベントの発生
[COM]
http://www.s34.co.jp/cpptechdoc/misc/comevent/
COMからのイベントを捕まえる方法
C#のソース側でイベントClick()を呼び出すのですが、
System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
のエラーとなってしまいます。
イベントClickがNULLとなっているためであると思います。
イベント通知時には一般的には、”+=”を用いてイベントハンドラの登録を行いますが、
この場合もやはり必要なのでしょうか。
クライアント(イベントシンク)側で実装しAdviseしたインターフェースとは
どのようにすれば結びつけられるのでしょうか。
いろいろ探したのですが、上記以上に具体的なサンプルは見つけられませんでした。
アドバイスをよろしくお願いします。
回答
-
yayadon様
アドバイスありがとうございます。
おかげさまで動作させることができました。
いただいたサンプルと、こちらの実装の差異について見てみましたが、
1)クラスをHogeClassとHogeClassEvにわけない。(VBからの呼び出しに余計なNew?)
2)Formクラスの中で、別途Newしたためインスタンスが2重化してしまった。
あたりが要因のように思います。
3)protected virtual void OnHogeActionを使用する。
これは直接関係ないようですね。以前WEBをあさっているときにイベントにおける
OnXXXXXXの働きについて見たことがあったので、ひょっとしてと思いましたが。
詳細については今後の課題です。
VBでの動作を確認後、C++COMにおいてもイベント受信を確認できました。
ありがとうございました。
すべての返信
-
状況がつかめない所がありますので確認させてください。
C#とC++、どちらがサーバーでどちらがクライアントなのでしょうか?
「C#からCOMの呼び出しはうまくいっている」から想像するとC#がクライアントとも思いますが
「C#をイベントソースとし、COMをイベントシンクとして、イベントの通知を行いたいのですが」から想像すると
C++のCOMがクライアントですよね?
両方を満たすとどちらもサーバーであり、クライントという状況になりますが、そうしてしまうと相互参照になり解放できなくなってしまいます。
それとも以下のような構成でしょうか?
C#(クライアント)
↓
C++(COM)
↓
C#クラスライブラリ(COMインターフェイス実装)
-
たぶん,うっかりミス 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 という命名の方が無難な気がするんですけどね。
-
この識別値は「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 サーバー」 からのイベントを
受け取れることを確認してみるのはどうでしょう?
-
WEB上の他のサンプルだと,(WTL を利用したものですが)
先のリンク先と同じ IDispEventImpl<> を利用した例が
イベントシンクのカプセル化 - IDispEventImpl
また,
IDispEventSimpleImpl<> を利用した例が
イベントシンクのカプセル化 - IDispEventSimpleImpl
にありますね。
ただWTLのクラス内にラップしている形になっているので,
WTL のことを知らないとかえって参考にならないかもしれませんが。
イベント通知時には一般的には、”+=”を用いてイベントハンドラの登録を行いますが、
この場合もやはり必要なのでしょうか。
上の例なら,
イベント・ソース・オブジェクト となるブラウザコントロールのIUnknownポインタ (m_pWB2) に対して
Code Snippet
DispEventAdvise(m_pWB2, &DIID_DWebBrowserEvents2);しているところです。(ATLの AtlAdvise 関数を呼んでます)
-
お世話になります。
その後、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はまだ荷が重いです。
-
上の例なら,
Managed COMサーバー側は,例えば,以下のような感じにします。Code Snippetusing 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 Snippetusing 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 SnippetOption 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
-
yayadon様
アドバイスありがとうございます。
おかげさまで動作させることができました。
いただいたサンプルと、こちらの実装の差異について見てみましたが、
1)クラスをHogeClassとHogeClassEvにわけない。(VBからの呼び出しに余計なNew?)
2)Formクラスの中で、別途Newしたためインスタンスが2重化してしまった。
あたりが要因のように思います。
3)protected virtual void OnHogeActionを使用する。
これは直接関係ないようですね。以前WEBをあさっているときにイベントにおける
OnXXXXXXの働きについて見たことがあったので、ひょっとしてと思いましたが。
詳細については今後の課題です。
VBでの動作を確認後、C++COMにおいてもイベント受信を確認できました。
ありがとうございました。