本記事は、COM Advent Calendar 2014 – Qiitaの20日目の記事です。


OBJREFモニカーはオブジェクトへの参照を扱うモニカーです。文字列で表現できるため、プロセス間での受け渡しに使用できます。

サンプルプログラム: C++からVBScriptへ

今回のサンプルでは、C++で実装したオブジェクトをVBScriptから使うというプログラムにしています。system関数でVBScriptを起動し、コマンドライン引数でモニカーの文字列を渡しています。

まず、オブジェクトを作るC++側のコードです。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <windows.h>
#include <comdef.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
#include <boost/scope_exit.hpp>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ATL::CComCoClass<Hoge>
  , public IDispatch
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()
 
  IFACEMETHOD(GetTypeInfoCount)(
    __RPC__out UINT*) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(GetTypeInfo)(
    UINT,
    LCID,
    __RPC__deref_out_opt ITypeInfo**) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(GetIDsOfNames)(
    __RPC__in REFIID,
    /*__RPC__in_ecount_full(cNames)*/ LPOLESTR*,
    __RPC__in_range(0,16384) UINT,
    LCID,
    /*__RPC__out_ecount_full(cNames)*/ DISPID*) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(Invoke)(
    _In_ DISPID dispIdMember,
    _In_ REFIID riid,
    _In_ LCID,
    _In_ WORD flags,
    _In_ DISPPARAMS *pDispParams,
    _Out_opt_ VARIANT* pVarResult,
    _Out_opt_ EXCEPINFO*,
    _Out_opt_ UINT*) override
  {
    if (dispIdMember != DISPID_VALUE || flags != DISPATCH_METHOD)
      return DISP_E_MEMBERNOTFOUND;
    if (riid != IID_NULL)
      return DISP_E_UNKNOWNINTERFACE;
    if (pDispParams->cArgs != 0)
      return DISP_E_BADPARAMCOUNT;
    if (pDispParams->cNamedArgs != 0)
      return DISP_E_NONAMEDARGS;
    VariantInit(pVarResult);
    std::cout << "Hoge::Invoke" << std::endl;
    return S_OK;
  }
};
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(
      nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE));
 
    // OBJREFモニカーを作る。
    IDispatchPtr hoge;
    ATLENSURE_SUCCEEDED(Hoge::CreateInstance(&hoge));
    IMonikerPtr moniker;
    ATLENSURE_SUCCEEDED(CreateObjrefMoniker(hoge, &moniker));
    hoge = nullptr;
 
    // 文字列を取り出す。
    IBindCtxPtr bc;
    ATLENSURE_SUCCEEDED(CreateBindCtx(0, &bc));
    LPOLESTR monikerStr;
    ATLENSURE_SUCCEEDED(moniker->GetDisplayName(bc, nullptr, &monikerStr));
    BOOST_SCOPE_EXIT_ALL(&monikerStr) { CoTaskMemFree(monikerStr); };
 
    // 子プロセスを作る。
    WCHAR sysDir[MAX_PATH] = {};
    GetSystemDirectoryW(sysDir, ARRAYSIZE(sysDir));
    std::wostringstream ss;
    ss << sysDir << L"\\cscript.exe //nologo objref.vbs /str:" << monikerStr;
    std::wcout << L"main: " << ss.str() << std::endl;
    _wsystem(ss.str().c_str());
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << std::hex << std::showbase;
    std::clog << e.m_hr << ' ' << ATL::AtlGetErrorDescription(e) << std::endl;
  }
 
  CoUninitialize();
}

CreateObjrefMoniker関数でIMonikerが得られ、それに対してIMoniker::GetDisplayName関数を呼び出すと文字列表現が得られます。

上のコード内から_wsystem関数で起動されるVBScriptのコードです。VBScriptでは、GetObjectでモニカーの文字列からオブジェクトを得られます。

objref = WScript.Arguments.Named("str")
WScript.Echo "--------"
WScript.Echo "objref.vbs: " & objref
Set hoge = GetObject(objref)
hoge()

実行すると以下のような感じになります。

main: C:\Windows\system32\cscript.exe //nologo objref.vbs /str:objref:TUVPVwEAAAA(中略)//AAAOAP//AAAAAA==:
--------
objref.vbs: objref:TUVPVwEAAAAA(中略)//AAAOAP//AAAAAA==:
--------
Hoge::Invoke

main:とobjref.vbs:の行は、確認用にC++側とVBScript側で出力させているものです。最終的に、C++で定義したHogeクラスのInvokeメンバ関数が呼び出されています。

今回の例はVBScriptだったのでIDispatchにしましたが、もちろんマーシャリング可能なインタフェースなら何でも可能です。プロセス間だと、たとえ双方がMTAでもマーシャリングが必要です。

モニカーからオブジェクトを得る関数など

プラットフォームごとのモニカーからオブジェクトを取得する関数などです。モニカーからのオブジェクトの取得は、多くの環境で関数・メソッド1つでできるようになっています。

その他の手法

プロセス間でオブジェクトを受け渡す方法はほかにもあります。OBJREFモニカーとの比較として、いくつか紹介します。

CoMarshalInterface関数

文字列に拘らなければ、CoMarshalInterface関数でも同様のことが可能なはずです。たとえば、試していませんが標準入出力経由で受け渡す方法があるでしょう。2015年4月20日追記:実際にやってみました。標準出力のパイプ経由でCOMインタフェースを受け渡す

バイナリではない(テキストだと保証されている)点ではOBJREFモニカーに軍配が上がります。一方、引数でマーシャルする範囲(スレッド・プロセス・マシン間)を指定できるのはCoMarshalInterfaceのほうが優れています。

Running Object Table

VBScriptのGetObject関数などでオブジェクトを取得できるようにしたいのであれば、Running Object Table (IRunningObjectTable)で任意のモニカーを登録する方法もあります。

これは用途によって使い分けるところだと思います。上記サンプルコードの用途で使うならば、CreateObjrefMonikerとGetDisplayNameよりは手順が多いので面倒です。

ポインターモニカー

オブジェクトへの参照を表現するモニカーという点では、ポインターモニカー (Pointer Monikers, CreatePointerMoniker)もあります。文字列化できないのがOBJREFモニカーとの違いです。

DCOMによるリモートアクセス

ところで、MSDNライブラリのCreateObjrefMonikerの説明にあるように、実はコンピュータ越しでも使えるようです。もちろん、デフォルトではWindowsのセキュリティが効いているはずです。

これについては私がまだ分かっていないので、次回以降に取り上げるかもしれません。追記:その後取り上げました。アクセス許可の指定でリモートアクセスを禁じる

おわりに

今回のサンプルコード、CoCreateInstance関数と対照的だと考えています。CoCreateInstanceでは、クライアントからサーバーに要求を出すことでプロセス間通信が始まります。一方、今回のサンプルコードは、オブジェクトを持っている側からクライアントとなる子プロセスを作り、そこにオブジェクトへの参照を引き渡しています。

実は、こういうことに使える手法がないかと探しているときにOBJREFモニカーを見つけました。

というわけで、今回はOBJREFモニカーとそれを使った子プロセスへのオブジェクト引き渡しがテーマでした。ここまで書いた今、ちょっと欲張りな内容だったと反省しています。

2015/01/18編集:モニカー文字列からオブジェクトを取り出す関数の紹介の箇所で、Windows APIの欄をCoGetObjectに差し替えました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ OBJREFモニカー
  • ⇒ OBJREFモニカー