Activex DLLのイベントを拾うには?

解決


SS  2006-04-22 04:29:09  No: 61329

VB6.0で作ったActiveX DLLの中で発生させたイベントを、VC++で作るダイアログexeの中で拾うにはどうすれば良いのでしょうか?

具体的には、VBのSampleX.dllのプログラムのclsSample内で例えば、

Event evtSample(nKeyCode As Integer)

と宣言し、

RaiseEvent evtSample(nKeyCode)

という記述で起こしたイベントを、VCのプログラムの中で拾いたいということです。

VBのexeであれば、そのDLLを参照設定し、
Private WithEvents objSample As SampleX.clsSample
を宣言し、
Set objSample = New SampleX.clsSample
としてインスタンス作成し、
Private Sub objSample_evtDllStoped(nKeyCode As Integer)
というイベントプロシージャを記述すれば拾えますよね?
それと同等のことをVC++のプログラム内でやりたいのですが・・・。


通りすがり  2006-04-22 04:44:38  No: 61330

επιστημηさんのサイト(http://www.s34.co.jp/cpptechdoc/)にCOMのイベント受信のサンプルがありますよ

Miscelaneous : 番外編の「COMからのイベントを捕まえる方法」って所


SS  2006-04-22 05:09:32  No: 61331

ありがとうございます。

ただ今回の件は、ActiveX DLLの方は既に作られているVBのもので、その既存のDLLを使用する前提なのです・・・。


kure  2006-04-22 05:43:15  No: 61332

επιστημηさんのサイトにあるのと
基本的にやり方は同じはずです。
単にイベントを発生させるCOMが異なるのと
イベントのインターフェイスが異なるってだけなはずです。

MFCが使えるなら自動インポートでラッパークラスを作ってもらい
IConnectionPointあたりを使ってイベントの受理部分を結びつける方法があります。
MFCが使えなかったり自動インポートができないのであれば、
oleview.exeを使ってDLLに定義されているインターフェイスを調べて、
がんばって実装する必要があります。

イベントインターフェイスを持つクラスを作ってそいつを
イベントを発生されるインスタンスにAdviseで結び付けてやる
っていう大まかなながれはMFCでも非MFCでも変わらないはずです。


SS  2006-04-22 06:17:37  No: 61333

ありがとうございます。

当方VC++はほぼ初心者なのですが、自動インポートというのはダイアログ上で右クリックしてClassWizardを出して、「クラスの追加」で「タイプライブラリから」を選び、そのActiveX DLLを指定してソースファイルを自動生成させることでしょうか?
(この場合、SampleX.Cpp、SampleX.hができる)


SS  2006-04-22 07:04:19  No: 61334

上記だと前提してラッパークラスを作り、επιστημηさんのサイトを参考にやってみたのですが、επιστημηさんのサイトで言うところの、

      CComPtr<ICounter> counter;
      hr = counter.CoCreateInstance(CLSID_Counter);

の部分をどうすれば良いかがわかりません。

      CLSIDFromProgID(OLESTR("SampleX.clsSample"), &clsid)

でクラスIDを取得し、

      _clsSample objSample;
      hr = objSample.CoCreateInstance(clsid);

とやってみたのですが、ビルド時に
「'CoCreateInstance' : '_clsSample' のメンバではありません。」
と怒られました。確かにメンバでは無いので、

      hr = CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(_clsSample),(LPVOID *) &objSample);

という方法でやってみたのですが、
「'_clsSample' : このオブジェクトに関連付けられた GUID はありません。」
と怒られます。

今回の場合、インスタンスはどのように作れば良いのでしょうか?


通りすがり  2006-04-22 11:14:52  No: 61335

ためしにOLE/COMObject ViewerでそのSampleXとやらのIIDとか見てみては?


SS  2006-04-24 23:38:28  No: 61336

ありがとうございます。

ただ、OLE/COMO bject Viewerにドラッグ&ドロップして見てみましたが、どこが通りすがりさんおっしゃるところのIIDにあたるのかがわかりませんでした。

IAsyncOperation
IClientSecurity
IDataObject
IMarshal
IMulutiQI
IProxyManager
IUnknown

というのにそれぞれ
Interface =
というのは出てますが・・・。 

またわかったとして、そのIIDを、CoCreateInstanceを呼ぶときに、

__uuidof(_clsSample)

の代わりに直接書いてパラメータとして与えればよいということでしょうか?


kure  2006-04-25 00:56:16  No: 61337

返事が遅くなってすみません。

> 上記だと前提してラッパークラスを作り

επιστημηさんのサイトのコードはMFCではなく
ATLのCComPtrを使っています。
MFCのラッパークラスを使うのであれば
COleDispatchDriverの機能を使います
(自動生成されたクラスはCOleDispatchDriverを継承します)。
詳しくはMSDNでCOleDispatchDriverの項を見てください。

COleDispatchDriverを継承したラッパークラスを使うと
CComPtr::CoCreateInstanceの部分を

// CWrapperClassはCOleDispatchDriverを継承してる
CWrapperClass comWrapper(); 

// 引数にはCLSIDを渡すのとProgID名を渡すのと両方使える
comWrapper.CreateDispatch(_T("SampleX.clsSample")); 

と書くことができます。


SS  2006-04-25 04:51:57  No: 61338

ありがとうございます。

括弧の有無だけ違うのですが、

// CWrapperClassはCOleDispatchDriverを継承してる
_clsSample comWrapper;

// 引数にはCLSIDを渡すのとProgID名を渡すのと両方使える
comWrapper.CreateDispatch(_T("SampleX.clsSample"));

でできました。
これでSampleX.dll内の関数を呼び出せるようにはなったのですが、
SampleX.dll内のイベントの取得法がまだわかりません。

VBのプログラムであるclsSample内で例えば、

Event evtSample(nKeyCode As Integer)

と宣言し、ソース内で

RaiseEvent evtSample(nKeyCode)

と記述してイベントを起こしている場合、
evtSampleはεπιστημηさんのサイトの例で言うどれにあたるのでしょうか?

また、επιστημηさんのサイトでインターフェイスとしてあるICounterは、今回のSampleX.Dllだと何にあたるのでしょうか?


SS  2006-04-25 05:01:37  No: 61339

なお、自動インポートしたときにSampleX.cppに、

void __clsSample::evtSample(short* nKeyCode)
{
        static BYTE parms[] =
                VTS_PI2;
        InvokeHelper(0x1, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
                 nKeyCode);
}

というソースは自動生成されたのですが、evtSampleイベントが発生するとこの関数が呼ばれるというわけではないですよね?
試しにこの関数の中に、

AfxMessageBox("イベント発生");

という記述を入れてみたのですが、evtSampleイベントが発生したと思われるタイミングでもメッセージボックスでなかったですし。


kure  2006-04-25 06:11:15  No: 61340

> evtSampleはεπιστημηさんのサイトの例で言うどれにあたるのでしょうか?

太字で強調表示されているFire_changedがVB側(正確にはCOMサーバー側)
のRaiseEventに相当します。
ここでInvokeされたイベントがCOMのRPC機構を通して
Client側のイベントハンドラクラスに通知されることになります。

> ICounterは、今回のSampleX.Dllだと何にあたるのでしょうか?

_clsSampleですね。
イベントハンドリング用のクラスは別にあるように思えます。
oleviewなどで探してみてください。

> evtSampleイベントが発生するとこの関数が

よばれません。
それはRPCでCOMサーバーに対してメソッド呼び出しを行うためのコードです。

通常、

1.何らかのメソッドを呼び出す(Client->Server)
2.サーバー内部処理(Server->Server)
3.変更通知イベントハンドラーが接続していたらそいつに対してInvoke(Server->Client)
4.変更通知イベントを受け取って何か処理をする(Client->Client)

となります。

επιστημηさんのサイトで紹介しているケースでは
ICounterはCOMサーバー(Server側)
CounterEventsはイベント受信クラス(Client側)
となります。

> SampleX.dll内のイベントの取得法

ですが、IConnectionPointContainerとIConnectionPointを用います。

具体的には、
IConnectionPointContainer* pContainer = NULL;
HRESULT hr = comWrapper.m_lpDispatch->QueryInterface(uuid, &pContainer);
if(SUCCEEDED(hr))
{
    IConnectionPoint* pPoint = NULL;
    hr = pContainer->FindConnectionPoint(イベントクラスのID, &pPoint);
    if(SUCCEEDED(hr))
    {
        // イベントハンドリングクラスを生成する
        myEventHandler = new MyEventHandler();
        LPUNKNOWN pUnknwon = eventHandler->GetInterface(&IID_IUnknown);
        if(NULL != pUnknown)
        {
            hr = pPoint->Advise(pUnknown, &cookie);
        }
    }
}

といった風になります。
このコードでは後始末をなにもやってないので
その辺は適宜補ってください。


SS  2006-04-25 23:31:17  No: 61341

ありがとうございます。

上記でMyEventHandlerと書いてあるのが、επιστημηさんのサイトで言うところの、CounterEventsですよね?
それを真似てイベントハンドリングクラスを宣言しようと(とりあえずCounterという名称も変えずに)、

#define SINKID_COUNTEREVENTS 0

class ATL_NO_VTABLE CounterEvents :
  public CComObjectRootEx<CComSingleThreadModel>,
  public IDispEventImpl<SINKID_COUNTEREVENTS, CounterEvents, 
                              &DIID__ICounterEvents, &LIBID_COUNTERSERVERLib, 1, 0>
{
public:
  CounterEvents() {}

という部分を自分のソースにもコピーしてみたのですが、
CComObjectRootExを使うためにatlcom.hをインクルードしても
ビルドでヘッダーの中でエラーが出てしまいます。
(例:'_Module' : 定義されていない識別子です。)
επιστημηさんはMFCではなくATLとのことで、上記部分も根本的に違う記述になるのでしょうか?

また、kureさんの書いてくださったソースの、

HRESULT hr = comWrapper.m_lpDispatch->QueryInterface(uuid, &pContainer);

のuuidの部分は、インターフェイスIDを渡すのだと思うのですが、具体的にはどのように取得すれば良いのでしょう?

上の方で通りすがりさんにもoleviewを見るように言われましたが、oleviewを見るのは初めてのため、kureさんのおっしゃるイベントハンドリング用のクラス含め、どれがそれにあたるのかわかりません。
例えばQueryInterfaceに渡すuuidは、oleviewでSampleX.dllを見ると出てくる、

Interface =
  {〜} = IUnknown  

の{〜}の部分のということでしょうか?


kure  2006-04-26 01:18:27  No: 61342

> 上記部分も根本的に違う記述になるのでしょうか?

Yes.

> uuidの部分は

これは私のミスです。
実際には__uuidof(IConnectionPointContainer)とすればOKです。

EventHandlerクラスのuuidが必要になるのは
FindConnectionPointのときです。

EventHandlerクラスについては
CCmdTargetを継承して、イベントハンドリングしたいすべてのメソッドを
定義したクラスを作成します。
その上で、BEGIN_DISPATCH_MAP〜END_DISPATCH_MAP内に
イベントに対応するメソッドの定義を行います。
また、BEGIN_INTERFACE_MAP〜END_INTERFACE_MAPにも
必要な記述を追加してください。

一応PowerPointのイベントを取得するサンプルがMSDNにあるので
参考用にURLを貼っておきますね。

http://support.microsoft.com/?scid=kb;ja;309309&spid=2648&sid=13

対象としているDLLが違うってだけで
基本的な流れは↑のリンク先と同じなはずです。


kure  2006-04-26 01:23:03  No: 61343

追記:
oleviewの使い方ですが
typelibが登録済みなら
ツリービューのType Librariesから目的のものを選択して
ダブルクリックまたは右クリック->Viewで
interfaceの一覧を見れます。

さらにinterfaceの詳細を見ると
そのinterfaceのuuidを見ることができます。

登録していないtypelibであればメニューのファイルから
View TypeLibを選択すればOKです。


SS  2006-04-26 05:32:05  No: 61344

何度も御丁寧にありがとうございます。

提示してくださったサイトを参考に、
MyEventHandlerクラスを追加してみました。
(参考サイトではCMyPPTEventsHandlerクラス)

それで、kureさんが書いてくださった部分の

>hr = pContainer->FindConnectionPoint(イベントクラスのID, &pPoint);

の部分のイベントクラスのIDですが、
それはクラス追加によってMyEventHander.cppに、

static const IID IID_IMyEventHandler =
{〜};

と自動生成された部分の定数
IID_IMyEventHandler
の値で良いのでしょうか?

ただ、MyEventHander.cpp内での定義のため、他のcppソースからでは使えないので、
参考サイトの項番6の様に、ビジネスロジックを入れるソース上にベタに、
上記の定数宣言部分を書き写してやってみました。

それでビルドして出来たexeで試してみたところ、
QueryInterface
まではうまく行くのですが、
FindConnectionPoint
は失敗しているようです。

これはどのような原因が考えられるでしょうか?

MyEventHandlerクラスを追加するとき、
特にSampleX.dllを参照させられるような行為はなかったのですが、
そういうものなのでしょうか?
※つまり、イベント受信クラスとSampleX.dllを結びつける記述は、
  ソース上のkureさんが書いてくださった部分で初めて現れる
  と認識していますが、それで合っていますか?

何か手順を間違った、もしくはIID_IMyEventHandlerの{〜}の部分を
SampleX.dllをoleviewで見て自分で何かに書き換えないと行けない等あるので参考サイトのしょうか?
項番17の下の方に、

>クラス ウィザード が最初に生成した値から IID IMyPPTEventsHandler 静的な定数の値が変更されるように注意します。 

という記述もありますし・・・。
ただ、oleviweで見た__clsSampleのuuid(〜)の値に
IID_IMyEventHandlerの{〜}の中身を書き換えてみたのですが、
ビルドで、
C2021: 浮動小数点定数の指数として使われている文字 'A' が、正しい数字ではありません。
と怒られてしまいました。
9EAD14F6-501F-11D5〜・・・
のような値なので、直接入れるものではないのかも知れませんが・・・。

なお、BEGIN_DISPATCH_MAP〜END_DISPATCH_MAPの部分は、
oleviewでSampleX.dllを見て、イベントのidが0x00000001だったので、

  DISP_FUNCTION_ID(MyEventHandler,"evtSample",0x00000001,evtSample,VT_EMPTY, VTS_PI2)

と記述しました。
BEGIN_INTERFACE_MAP〜END_INTERFACE_MAPには、クラス生成時に、

  INTERFACE_PART(MyEventHandler, IID_IMyEventHandler, Dispatch)

という記述が自動で書き込まれていたので、参考サイトを見る限りこのままで良さそうだったので追記していません。


SS  2006-04-26 05:48:31  No: 61345

上記、

>9EAD14F6-501F-11D5〜・・・

を、コンマ区切りの16進数表記に直してやったら通りました。
ありがとうございました。


SS  2006-04-26 05:52:36  No: 61346

新しくスレ立てるようなものでもないので最後にここで質問させてください。

oleviewで見えるuuidは、右クリックでコピー等できないようですが
これは目で見て手で書き写すしかないのでしょうか?

だとすると誤り等結構おきそうですよね・・・。


通りすがり  2006-04-26 09:17:03  No: 61347

左側のツリーでID取りたいものを右クリックすると
「Copy GUID To Clipboard」や「Copy CLSID To Clipboard」がありますよ


kure  2006-04-26 19:30:24  No: 61348

既に解決マーク付きですが参考までに

> イベント受信クラスとSampleX.dllを結びつける記述

これについてはCreateInstanceした時点で
uuidやProgIDを元にレジストリの検索が行われ、
対象となるCOM(中身はdllやらocxやらtlbやら)のロードが行われます。
この辺の処理はMFCやATLやらWin32APIがCOMの機構を隠蔽してくれるので
外からは見えません。

> コンマ区切りの16進数表記に直して

ID構造体の中身はただのWORDとDWORDの配列なので
文字列として書いたらあかんです。

> これは目で見て手で書き写すしかないのでしょうか?

通りすがりさんも書かれてますが、
Copy用のメニューがあるのでそれを使ってください。
ただし、C++用の16進数への変換まではしてくれないので
そこは手でやる必要があります。


SS  2006-04-28 05:40:49  No: 61349

通りすがりさん、kureさん、ありがとうございます。

ただoleviewについてですが、

>登録していないtypelibであればメニューのファイルから
>View TypeLibを選択すればOKです。

のやり方で見た場合、右クリックしてもCopy用のメニュー出なくないですか?


kure  2006-04-28 19:08:38  No: 61350

> 右クリックしてもCopy用のメニュー出なくないですか?

それはITypeLib Viewerのこと?
だったらどんなやりかたしてもCopyメニューはでませんよ。
Copy用のメニューはOLE/COM Object Viewerじゃないと出ません。


ななし  2006-04-28 19:33:26  No: 61351

>ただ、MyEventHander.cpp内での定義のため、他のcppソースからでは使えないので、
externとか


SS  2006-04-29 00:04:32  No: 61352

>kureさん

そうでした。別物なのですね。すみません。
OLE/COM Object ViewerのメニューのBind to Fileで登録できるのかと思ってSampleX.dllを選択してみたのですが、

Bad extension for file
 MK_E_INVALIDEXTENSION($800401E6)

が出て失敗してしまいます。拡張子がまずいのでしょうか?

>ななしさん

MyEventHander.cppの宣言はそのままで、
単純にビジネスロジックソースファイルの方を
externにしただけではだめでした。(ビルドで未解決ですと言われる)

具体的にはどのようにすれば良いのでしょうか?


SS  2006-04-29 00:04:32  No: 61353

>kureさん

そうでした。別物なのですね。すみません。
OLE/COM Object ViewerのメニューのBind to Fileで登録できるのかと思ってSampleX.dllを選択してみたのですが、

Bad extension for file
 MK_E_INVALIDEXTENSION($800401E6)

が出て失敗してしまいます。拡張子がまずいのでしょうか?

>ななしさん

MyEventHander.cppの宣言はそのままで、
単純にビジネスロジックソースファイルの方を
externにしただけではだめでした。(ビルドで未解決ですと言われる)

具体的にはどのようにすれば良いのでしょうか?


kure  2006-04-29 00:46:43  No: 61354

> 拡張子がまずいのでしょうか?

エラーだけ見るとそうらしいですね。

> externにしただけではだめでした。

MyEventHandler.cppでのIDの定義でstaticをつけてるとかかな?
staticをつけるのがだめな理由については
「リンケージ」について調べてみてください。


SS  2006-04-29 06:54:24  No: 61355

>MyEventHandler.cppでのIDの定義でstaticをつけてるとかかな?

はい。そうだったので、staticを取って、かつ

const IID IID_IMyEventHandler = {〜};

を、MyEventHandler.hに移してビルドしてみたら出来ました。

ただ、自動で生成された部分を、勝手に移したこのやり方で良いものなのでしょうか?
また、良いものだとした場合、場所はMyEventHandler.hのどのあたりに移すのが普通でしょうか?
とりあえずクラス宣言の直上あたりにしましたが・・・。


kure  2006-04-29 08:37:46  No: 61356

> MyEventHandler.hに移して

とりあえずはそれでもいいんだけど、
MyEventHandler.hをインクルードするファイルが複数あると
同じシンボル名の実体が複数できてコンパイルエラーが起きて
しまいますね。
こういうときは.hにexternで宣言だけを行い、
.cppに定義を行います。

例)

[sample.h]
extern const IID IID_MyEventHandler;

[sample.cpp]
const IID IID_MyEventHandler = { ... };

[app.cpp]
#include "sample.h"
void hoge() {
    // IID_MyEventHandlerを使えるよ;
}

> ただ、自動で生成された部分を、勝手に移したこのやり方で良いものなのでしょうか?

externで外部に開示しないとIIDを参照できないので問題無しです。

> のどのあたりに移すのが普通

クラスとIIDには直接的な関係はありません。
したがってどこに移しても構いません。


SS  2006-05-10 23:50:54  No: 61357

お返事遅れてすみません。

kureさん、本当にありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加