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


OBJREFモニカーの回で、コンピュータ間のマーシャリングもできるらしいことを書きました。今回はそれを試してみることにしました。というわけで、結局DCOMに足を突っ込んでしまいました。

2台のWindowsマシンを用意します。接続を待ち受けるC++側と接続しに行くVBScript側です。以下の準備を行っておきます。

  • 双方を同じLANに接続する。
  • 双方で同じ名前・パスワードのユーザーを作り、そこでプログラムを実行する。ただし、デフォルトではパスワードが空だとうまくいきません。
  • C++プログラムを実行する側では、Windowsファイアウォールなどを切っておくか、除外設定を行う。
  • もしDCOMを無効にしていたら、有効に戻す。参考:Geekなぺーじ:DCOM(分散COM)を無効にする

C++側プログラムは以下です。main関数以外は前々回(OBJREFモニカー)とほぼ同じです。OBJREFモニカーの文字列を標準出力に表示して、Enterが入力されるまで待ち受ける、という内容です。

#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;
  }
};
 
std::wstring GetObjrefMonikerDisplayString(IUnknown* obj)
{
  IMonikerPtr moniker;
  ATLENSURE_SUCCEEDED(CreateObjrefMoniker(obj, &moniker));
  IBindCtxPtr bc;
  ATLENSURE_SUCCEEDED(CreateBindCtx(0, &bc));
  LPOLESTR monikerStr;
  ATLENSURE_SUCCEEDED(moniker->GetDisplayName(bc, nullptr, &monikerStr));
  BOOST_SCOPE_EXIT_ALL(&monikerStr) { CoTaskMemFree(monikerStr); };
  return monikerStr;
}
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(
      nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE));
 
    IDispatchPtr hoge;
    ATLENSURE_SUCCEEDED(Hoge::CreateInstance(&hoge));
 
    std::wstring monikerStr = GetObjrefMonikerDisplayString(hoge);
    ATLENSURE_SUCCEEDED(CoLockObjectExternal(hoge, TRUE, FALSE));
 
    std::wcout << monikerStr << std::endl;
 
    std::cin.get();
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << std::hex << std::showbase;
    std::clog << e.m_hr << ' ' << ATL::AtlGetErrorDescription(e) << std::endl;
  }
 
  CoUninitialize();
}

VBScript側は以下です。前々回と変わっていません。cscript objref.vbs /str:モニカーのように、コマンドライン引数でモニカーを指定します。

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

これで動かしてみました。まず、1台目のコンピュータでC++プログラムを起動しておきます。次に、画面に表示されたOBJREFモニカーを2台目のコンピュータで、objref.vbsのコマンドライン引数に書き写し、起動するという手順です。

すると、無事(?)C++側でHoge::Invokeが出力され、Hoge::Invoke関数が呼び出されたことが確認できました。CoLockObjectExternalしているため、VBScriptを実行するたび、何度でも動きます。


認証が掛かっているとは言え、これはちょっとやめてほしい場合もあると思います。というわけで、次回はCOMサーバープロセスにおいて他コンピュータからのアクセスを禁じる話の予定です。

スポンサード リンク

この記事のカテゴリ

  • ⇒ OBJREFモニカーによるコンピュータ間の通信を試す