none
アウトプロセスCOMサーバーにおける接続ポイントの実装について RRS feed

  • 質問

  • COMについてお聞きしたいです。
    現在アウトプロセスCOMサーバーを使ったイベント処理を実装しているのですが、
    http://msdn.microsoft.com/ja-jp/magazine/cc163361.aspx
    上記サイトのサンプルを参考にしていたのですが
    アウトプロセスサーバーだと
    IConnectionPoint::Advise がE_NOTIMPLを返してしまい、
    処理がそこで止まってしまいます。


    これは、イベントシンクGetTypeInfo実装部のE_NOTIMPLが
    返ってきているところまではわかったのですが
    そもそもインプロセスサーバーの場合はイベントシンクのGetTypeInfo
    自体が呼ばれません。


    アウトプロセスCOMサーバーの場合の接続ポイントの実装について
    行き詰っており、解決策をご提示いただければ幸いです。
    2012年4月3日 16:02

回答

  • あ、原因わかった。QueryInterface をきちんと実装してないからだ。IUnknown/IDispatch と、_AddEvents 以外のインターフェースはないって答えないと、ダメです。

    もしかしたら、_AddEvents もダメかもしれませんが、こっちは試してみないとわかりません。

    アウトプロセスのCOMサーバーとやり取りする場合、手抜き実装はほぼ確実に失敗します。かなりシビア(その代りプロセス間通信としては高速の部類)な実装が要求されるので。

    なので、可能ならば、MFCやATLなどの実装をサポートしてくれるライブラリの利用をお勧めします。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク あま1 2012年4月4日 12:06
    2012年4月4日 10:06

すべての返信

  • タイプライブラリは登録していますか?おそらく、呼び出し元がIDispatch/IUnknown ではないインターフェースIDを渡してAdviseを呼び出したため、プロセス間を転送するためのタイプライブラリを探しに行って失敗(見つからないすなわち誰も実装していない!)を返していると思われます。

    ATL であれば、_AtlModule.RegisterServer() をしているはずです。呼び出し部分を、RegisterServer(TRUE); に変更して、登録すれば成功すると思いますよ。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 2:14
  • とっちゃんさん ご回答ありがとうございます。


    私の勉強不足でまだよく理解できないところがあるのですが
    お聞かせください。


    以下クライアント側の記述より抜粋ですが
    --------------------------------------------
    //COMサーバー名 → TestComServer


    #import "../TestComServer/Debug/TestComServer.exe" raw_interfaces_only, raw_native_types, no_namespace, named_guids


    HRESULT  hr;
    IAdd                      *pAdd;


    IConnectionPointContainer *pCPC;
    IConnectionPoint          *pCP;
    IUnknown                  *pSinkUnk;
    CSink              *pSink;
    DWORD  dwAdvise=0;

    CoInitialize(NULL);
    hr = CoCreateInstance(CLSID_Add, NULL, CLSCTX_ALL, IID_IAdd, (void **)&pAdd);
    hr = pAdd->QueryInterface(IID_IConnectionPointContainer,(void **)&pCPC);
    hr = pCPC->FindConnectionPoint(DIID__IAddEvents,&pCP);
    if(pCPC != NULL) pCPC->Release();
    pSink = new CSink();
    hr = pSink->QueryInterface (IID_IDispatch,(void **)&pSinkUnk);
    hr = pCP->Advise(pSinkUnk,&dwAdvise); //ここでE_NOTIMPLが返る
    -----------------------------------------------------------
    ※CSinkは_IAddEventsを継承します。
    ※インプロセスサーバーの場合は#importのexeがdllになります。

    IDLは以下の通りです。
    -------------------------------
    // TestComServer.idl : TestComServer の IDL ソース
    //


    // このファイルは、タイプ ライブラリ (TestComServer7_EventFire.tlb) およびマーシャリング コードを
    // 作成するために MIDL ツールによって処理されます。


    import "oaidl.idl";
    import "ocidl.idl";


    [
    object,
    uuid(1B1A39CD-DE13-404F-9971-2DF59F5F61FB),
    dual,
    nonextensible,
    helpstring("IAdd インターフェイス"),
    pointer_default(unique)
    ]
    interface IAdd : IDispatch{
    [id(1), helpstring("メソッド Add")] HRESULT Add([in] int nFirst, [in] int nSecond);
    };
    [
    uuid(1FD3DE87-13C9-4FB5-AE93-84ED8012947D),
    version(1.0),
    helpstring("TestComServer 1.0 タイプ ライブラリ")
    ]
    library TestComServer
    {
    importlib("stdole2.tlb");
    [
    uuid(AD5F800A-DDFC-4B63-ABE9-C80786A3C24E),
    helpstring("_IAddEvents インターフェイス")
    ]
    dispinterface _IAddEvents
    {
    properties:
    methods:
            [id(1), helpstring("AdditionStarted")]HRESULT AdditionStarted();
            [id(2), helpstring("AdditionStarted")]HRESULT AdditionCompleted(int nResult);
    };
    [
    uuid(DBC3E51B-99D6-4582-9421-2623C2BDC9F8),
    helpstring("Add Class")
    ]
    coclass Add
    {
    [default] interface IAdd;
    [default, source] dispinterface _IAddEvents;
    };
    };
    -----------------------------------------------------------

    DIID__IAddEventsがIDispatch/IUnknownではないインターフェースIDと捉えて間違いないでしょうか?
    _AtlModule.RegisterServer()の記述についてはお恥ずかしながら見つかりませんでした。
    どこに記述するものなのでしょうか・・・


    2012年4月4日 3:51
  • RegisterServer はサーバー側です。

    プロジェクトの種類がわからないので、どういう形でソースに書かれているかはわかりません。ATLじゃないと段取りも違うし。。。

    >DIID__IAddEvents

    はい。こちらがイベント用のインターフェース(IDispatchの派生インターフェース)にあたります。ところで。。。コネクションポインタなどは取得できてるんですよね?

    エラーチェックしてないようですけど。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 5:21
  • とっちゃんさん ご回答ありがとうございます。


    環境を書いていませんでした。申し訳ありません。
    Windows7 64bit     VS2005 C++ 
    Server側はATLプロジェクトで実行可能なアプリケーションとして作成し
    ATLシンプルオブジェクトとしてAddクラスを「接続ポイント」を選択し追加しています。


    エラーチェックに関してはここに書き込む際に行数節約と見やすくするために削除しました。
    その点について記載せず申し訳ありませんでした。


    Adviseを呼ぶまではすべてS_OKが返ってきています。

    2012年4月4日 5:52
  • 手元に2005があったので、適当にプロジェクト起こしてみました。登録は、ATLのデフォルトのままだと、RegisterServer(TRUE); で呼ばれますね。

    とすると。。。IDが異なってるか。。。

    あ、もしかして、その環境に登録されていないとかかも。。。

    「管理者権限」でコマンドプロンプトを開き

    EXEのあるフォルダで

    TestComServer /RegServer

    として、自己登録させてみてください。

    それでうまくいく気がします。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 6:16
  • とっちゃんさん ご回答ありがとうございます。

    TestComServer /RegServer かけてみましたが結果変わらずでした。

    ビルド後のイベントでRegServerは登録してあります。

    とっちゃんさんの環境ですとAdviseはきちんとS_OKが返ってきているのですか?

    2012年4月4日 6:36
  • いえ。プロジェクトを起こしてみただけでビルドはしてません。クライアント側も用意してないです。

    VS2005は、もうまともにビルドできる環境じゃないんで。。。(アンインストールするといろいろ環境がグダグダになるんで抜けないだけという。。。w)

    登録はできているとすると、あとはどこで引っかかるんだろう?COMサーバーとして起動はするみたいですし。。。

    エラーが発生するのは、サーバー側のGetTypeInfo ですよね?出力画面になにかメッセージ出てませんか?

    エラーが発生する状況なら、何かしらトレースメッセージ出してると思うのですが。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 7:23
  • E_NOTIMPLが応答されたことを示す

    TestClient.exe の 0x7538b9bc で初回の例外が発生しました: 0x80004001: 実装されていません

    が出るだけですね・・・

    COMの登録はできていると思います。

    pAdd->hoge();は呼べますしデバックでhoge内にもいけました。


    Advise関数についてですが、インプロセスサーバーでのデバックですと

    pCP->Advise(pSink,&dwAdvise); からステップインで

    atlcom.h の

    STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise(IUnknown* pUnkSink,DWORD* pdwCookie)

    内部にいけるのですが

    アウトプロセスサーバーですとステップインしても

    atlcom.hにいけずにE_NOTIMPLが返ってきます

    (イベントシンクのCSink内のGetTypeInfoにブレークをしかけておくとそこで止まりますが・・)

    インプロセスとアウトプロセスで根本的に接続方法が違うんですかね・・・
    参考書や解説サイトはたくさんみてきたのですが、サンプルはみんなDLLでの実装でして・・


    2012年4月4日 7:52
  • アウトプロセスとは、自分のプロセスの外を指します。通常のデバッグでは、1プロセスしか対象にしませんので、クライアント側でデバッグを開始していればサーバー側はデバッグできません(逆も同様)。

    VSでクライアント・サーバー側の両方をデバッグする場合は、管理者モードでVSを実行し、オプションのデバッグ-ネイティブにある、「RPCデバッグを有効にする」にチェックをつけておきます。

    この状態でデバッグ(たしか実際にデバッグするときは管理者じゃなくても行けた気がするけどできたかどうか覚えてない)で、ステップインすると、サーバー側のデバッグを行えるようになります(途中アセンブラコードの中を抜けていったはず。。。)。

    GetTypeInfo は、クライアント側のですか?クライアント側にタイプライブラリの要求は飛ばなかったはずなんですが。。。

    うーん。。。もしかして、同じGUIDを使って、DLL版とEXE版を作っていたりします?


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 8:19
  • とっちゃんさん ご回答ありがとうございます。

    GUIDは違うものをつかっています。

    DLLとEXEでプロジェクトを別にしてあります。

    RPCデバックについてはしりませんでした。

    いままではソリューションのプロパティからマルチスタートアッププロジェクトを設定するくらいしかしらなかったです。

    GetTypeInfo はクライアント側です。

    正確には、クライアント側でイベント受信用に作成したイベントシンクであるCSinkクラスの

    GetTypeInfoです。中はreturn E_NOTIMPLのみ

    CSinkは_IAddEventsを継承しており、_IAddEventsはIDispatchを継承しています。

    (いるはず・・ 

    2012年4月4日 8:35
  • んー。。。GetTypeInfoに行くのがわからないなぁ。

    Advise 呼び出しで渡すポインタは、IUnkown* のはずなんですよねぇ。。。

    サーバー側であらかじめデバッグしておいて、Advise メソッド(atlcom.hに定義があると思います)にブレークポイントを張ってみてはいかがでしょう?サーバー側で何かあるとすればその段階まで来てエラーになってると思います。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 9:18
  • とっちゃんさん ご回答ありがとうございます。

    atlcom.hのAdviseにはブレークを仕掛けてあるのですが、まったくかからないのです・・・

    (TestComServer.exeをデバックで起動後にTestClient.exeを起動する方法で確認

     確認用に組み込んでおいた pAdd->Hoge() には止まります)

    そもそもここまで到達できてないのですかね・・

    クライアント側の記述については以前の返信の通りです。

    CSinkについては以下の通りです。(マイクロソフトサンプルのままですが・・)

    class CSink : _IAddEvents
    {
        private:
        DWORD       m_dwRefCount;
        public:
    
    	CSink::CSink() {m_dwRefCount = 0;}
    	CSink::~CSink()	{}
    
    	HRESULT STDMETHODCALLTYPE AdditionStarted()
        {
    		printf("C++ SINK: Addition started event fired ... \n");
            return S_OK;;
        };
    
    	HRESULT STDMETHODCALLTYPE AdditionCompleted(int nResult)
        {
    		printf("C++ SINK: Addition completed event fired ... \n");
    		printf("C++ SINK: Addition result: %d \n",nResult);
            return S_OK;
        };
    
    	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
        {
            m_dwRefCount++;            
            *ppvObject = (void *)this;
            return S_OK;
        };
    
    	ULONG STDMETHODCALLTYPE AddRef()
        {
            m_dwRefCount++;
            return m_dwRefCount;
        };
        
    	ULONG STDMETHODCALLTYPE Release()
        {
            ULONG l;
            l  = m_dwRefCount--;
            if ( 0 == m_dwRefCount)
            {
                delete this;
            }
            return m_dwRefCount;
        };
    
    	HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pI)
    	{
    		return E_NOTIMPL;
    	};
    
    	HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID  riid, 
    											OLECHAR FAR* FAR*  rgszNames, 
    											unsigned int  cNames, 
    											LCID   lcid,
    											DISPID FAR*  rgDispId)       
    	{
    		return E_NOTIMPL;
    	}
    	HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTinfo, LCID lcid, ITypeInfo FAR **ppTIinfo)
    	{
    		return E_NOTIMPL;
    	}
    
    	HRESULT STDMETHODCALLTYPE Invoke(DISPID  dispIdMember, 
    									REFIID  riid, 
    									LCID  lcid, 
    									WORD  wFlags,
    									DISPPARAMS FAR*  pDispParams, 
    									VARIANT FAR*  pVarResult,
    									EXCEPINFO FAR*  pExcepInfo,
    									unsigned int FAR*  puArgErr)
    	{
    		HRESULT hr = S_OK;
    		if(pDispParams)
    		{
    			switch (dispIdMember)
    			{
    			case 1:
    				return AdditionStarted();
    			case 2:
    				return AdditionCompleted(pDispParams->rgvarg[0].iVal);
    			default:
    				return E_NOTIMPL;
    			}
    		}
    		return E_NOTIMPL;
    	}
    	
    };

    今デバックをしていてわかったことがあるので追記致します。
    pCP->Advise 時のpCPをクイックウォッチしたところ以下のようになりました。


    //インプロセスCOMサーバー
    -pCP 0x01f01354 IConnectionPoint *
     -IUnknown {...} IUnknown
      -__vfptr 0x1002eb00 const ATL::CComObject<class CAdd>::`vftable'{for `CProxy_IAddEvents<class CAdd>'}*
    [0] 0x100119c9 ATL::IConnectionPointImpl<class CAdd,&struct __s_GUID const _GUID_3e87f605_c3e6_491a_b972_b869617ec81b,class ATL::CComDynamicUnkArray>::_LocCPQueryInterface(struct _GUID const &,void * *)*
    [1] 0x10011708 [thunk]:ATL::CComObject<class CAdd>::AddRef`adjustor{4}' (void)*
    [2] 0x10011c0d [thunk]:ATL::CComObject<class CAdd>::Release`adjustor{4}' (void)*




    //アウトプロセスCOMサーバー
    -pCP 0x00496244  IConnectionPoint *
      -IUnknown  {...} IUnknown
       -__vfptr 0x75d970c8 *
    [0] 0x75e9d208 *
    [1] 0x75e9bf1b *
    [2] 0x75e9d36f *




    アウトプロセスCOMサーバーの時にvfptr[0]~[2]の値にIConnectionPointImpl等が書かれていません
    ここまでの処理はまったく同じなのに・・・



    • 編集済み あま1 2012年4月4日 10:06
    2012年4月4日 9:44
  • あ、原因わかった。QueryInterface をきちんと実装してないからだ。IUnknown/IDispatch と、_AddEvents 以外のインターフェースはないって答えないと、ダメです。

    もしかしたら、_AddEvents もダメかもしれませんが、こっちは試してみないとわかりません。

    アウトプロセスのCOMサーバーとやり取りする場合、手抜き実装はほぼ確実に失敗します。かなりシビア(その代りプロセス間通信としては高速の部類)な実装が要求されるので。

    なので、可能ならば、MFCやATLなどの実装をサポートしてくれるライブラリの利用をお勧めします。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク あま1 2012年4月4日 12:06
    2012年4月4日 10:06
  • とっちゃんさん ご回答ありがとうございます。


    現在QueryInterfaceの実装部を書き換えて
    正常にイベントが動作していることを確認できました。
    ありがとうございました。


    イベントシンクの実装は全部作らないといけないと思っていたのですが、
    ATLの実装のサポートで作るほうがよかったのですね。(ATLシンプルオブジェクトとかでしょうか)


    ATLをつかったイベントシンクの作成をしてみようと思います。
    長時間お付き合いしていただき大変ありがとうございました。

    2012年4月4日 11:36
  • ATLでは、多くの場合 IDispEventImpl クラスを使って実装になると思います。でも、ATLでイベントシンク書いたことないんでよくわからんです。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 11:53
  • とっちゃんさん ご回答ありがとうございます

    IDispEventImpl ですか、さっそく調べてみたいと思います。 

    繰り返しですが、長時間のご回答大変ありがとうございました。    

    大変助かりました。   

    2012年4月4日 12:06