プロセス越しにCOMインタフェースを受け渡す話、久しぶりにします。今度はCoMarshalInterfaceCoUnmarshalInterfaceです。この間でのデータの受け渡しにCreatePipeによるパイプを使いました。

今回のサンプルコード、OBJREFモニカーのときとは少し処理の流れが異なります。

  1. 親プロセスp.exe (p.cpp): 子プロセスc.exe (c.cpp)を起動。この際、標準出力を無名パイプでリダイレクトしておく。
  2. 子プロセスc.exe: オブジェクトを作り、CoMarshalInterfaceでの結果を標準出力に書き込む。
  3. 親プロセスp.exe: 無名パイプからCoUnmarshalInterfaceでインタフェースへのポインタを読み出す。
  4. 親プロセスp.exe: 上で得られたポインタに対してメソッド呼び出し。
  5. 子プロセスc.exe: メソッドが呼び出される。

まず、HANDLEをラップするIStream実装です。CoMarshalInterfaceとCoUnmarshalInterfaceはIStreamでマーシャル用データを受け渡すためです。親・子プロセス共通で使います。ReadとWriteだけ実装し、残りはE_NOTIMPLで構いません。

// HandleStream.h
#pragma once
 
#include <atlbase.h>
#include <atlcom.h>
 
class ATL_NO_VTABLE HandleStream
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModelNoCS>
  , public IStream
{
public:
  BEGIN_COM_MAP(HandleStream)
     COM_INTERFACE_ENTRY(ISequentialStream)
     COM_INTERFACE_ENTRY(IStream)
  END_COM_MAP()
 
  STDMETHOD(Read)(
    _Out_writes_bytes_to_(cb, *pcbRead) void* pv,
    _In_ ULONG cb,
    _Out_opt_ ULONG* pcbRead) override
  {
    DWORD read;
    if (!ReadFile(m_h, pv, cb, &read, nullptr))
      return ATL::AtlHresultFromLastError();
    if (pcbRead != nullptr)
      *pcbRead = read;
    return read < cb ? S_FALSE : S_OK;
  }
  STDMETHOD(Write)(
    _In_reads_bytes_(cb) const void* pv,
    _In_ ULONG cb,
    _Out_opt_ ULONG* pcbWritten) override
  {
    DWORD written;
    if (!WriteFile(m_h, pv, cb, &written, nullptr))
      return ATL::AtlHresultFromLastError();
    if (pcbWritten != nullptr)
      *pcbWritten = written;
    return S_OK;
  }
  IFACEMETHOD(Seek)(
    LARGE_INTEGER dlibMove,
    DWORD dwOrigin,
    _Out_opt_ ULARGE_INTEGER* plibNewPosition) override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(SetSize)(
    ULARGE_INTEGER libNewSize) override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(CopyTo)(
    _In_ IStream* pstm,
    ULARGE_INTEGER cb,
    _Out_opt_ ULARGE_INTEGER* pcbRead,
    _Out_opt_ ULARGE_INTEGER* pcbWritten) override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(Commit)(DWORD /*grfCommitFlags*/) override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(Revert)() override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(LockRegion)(
    ULARGE_INTEGER /*libOffset*/,
    ULARGE_INTEGER /*cb*/,
    DWORD /*dwLockType*/) override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(UnlockRegion)(
    ULARGE_INTEGER /*libOffset*/,
    ULARGE_INTEGER /*cb*/,
    DWORD /*dwLockType*/) override
  {
    return E_NOTIMPL;
  }
  IFACEMETHOD(Stat)(
    _Out_ STATSTG* pstatstg,
    DWORD grfStatFlag) override
  {
    if (pstatstg != nullptr)
      *pstatstg = {};
    return E_NOTIMPL;
  }
  IFACEMETHOD(Clone)(_COM_Outptr_ IStream** ppstm) override
  {
    if (ppstm != nullptr)
      *ppstm = {};
    return E_NOTIMPL;
  }
 
  HANDLE m_h = nullptr;
};

次に、子プロセス側です。C++/CLIでIDispatch実装しています。IDispatch実装に.NETが便利なので使いました。

// cl /clr p.cpp
#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <windows.h>
#include "HandleStream.h"
 
using namespace System;
using namespace System::Runtime::InteropServices;
 
public ref class Hoge
{
public:
  // これが親プロセスから呼び出される関数
  [DispId(DISPID_VALUE)]
  void f()
  {
    MessageBox(nullptr, L"Hoge::f", L"c.exe", 0);
  }
};
 
[MTAThread]
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, int)
{
  auto obj = gcnew Hoge;
  ATL::CComPtr<IDispatch> disp;
  disp.Attach(static_cast<IDispatch*>(
    Marshal::GetIDispatchForObject(obj).ToPointer()));
 
  ATL::CComObjectStack<HandleStream> s;
  s.m_h = GetStdHandle(STD_OUTPUT_HANDLE);
  Marshal::ThrowExceptionForHR(CoMarshalInterface(
    &s, __uuidof(disp.p), disp, MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL));
 
  MessageBox(nullptr, L"OKを押すと終了します", L"c.exe", 0);
}

最後に親プロセス側です。

// cl p.cpp
#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <windows.h>
#include <atlbase.h>
#include <atlstr.h>
#include <atlutil.h>
#include "HandleStream.h"
 
HRESULT OutputErrorMessgae(_In_ PCWSTR functionName, HRESULT hr)
{
  std::wclog << functionName << '\n';
  std::wclog << std::showbase << std::hex << hr << '\n';
  std::wclog << ATL::AtlGetErrorDescription(hr).GetString() << std::endl;
  return hr;
}
 
HRESULT OutputLastError(_In_ PCWSTR functionName)
{
  auto hr = ATL::AtlHresultFromLastError();
  return OutputErrorMessgae(functionName, hr);
}
 
int main()
{
  std::wclog.imbue(
    std::locale(std::locale::classic(), "", std::locale::ctype));
  auto hrInit = CoInitializeEx(
    nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
  if (FAILED(hrInit))
  {
    return hrInit;
  }
 
  ATL::CHandle hOutputRead, hOutputWrite;
  if (!CreatePipe(&hOutputRead.m_h, &hOutputWrite.m_h, nullptr, 0))
  {
    return OutputLastError(L"CreatePipe");
  }
  if (!SetHandleInformation(
    hOutputWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
  {
    return OutputLastError(L"SetHandleInformation");
  }
 
  STARTUPINFO si{sizeof si};
  si.dwFlags = STARTF_USESTDHANDLES;
  si.hStdOutput = hOutputWrite;
  PROCESS_INFORMATION pi{};
  if (!CreateProcess(L"c.exe", nullptr,
    nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi))
  {
    return OutputLastError(L"CreateProcess");
  }
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
 
  ATL::CComObjectStack<HandleStream> s;
  s.m_h = hOutputRead;
 
  ATL::CComPtr<IDispatch> disp;
  auto hrUnmarshal = CoUnmarshalInterface(&s, IID_PPV_ARGS(&disp));
  if (FAILED(hrUnmarshal))
  {
    return OutputErrorMessgae(L"CoUnmarshalInterface", hrUnmarshal);
  }
 
  DISPID dispid = DISPID_VALUE;
  auto hr = disp.Invoke0(dispid); // Hoge::fの呼び出し
  if (FAILED(hr))
  {
    return OutputErrorMessgae(L"Hoge::f", hr);
  }
  disp.Release();
  std::cout << "完了" << std::endl;
 
  CoUninitialize();
}

オブジェクト・ハンドルの寿命管理やエラー処理が大雑把な点はご容赦ください。パイプの読み書きにはタイムアウトなど強制切断する手段を用意したほうが良いのかもしれません。XPを忘れればCancelSynchronousIoがあるので難しくないでしょう。

IDispatchを使っていますが、もちろん任意のインタフェースが使用可能です。IDispatchはプロキシ・スタブを作らなくてもマーシャリングできるので、サンプルコードを書く際に便利なのです。

CoMarshalInterfaceは以前OBJREFモニカーの記事で関数名だけ登場させました。気になっていたので実際に試してみたというわけです。

2015年4月19日追記:OutputErrorMessgaeの出力が文字化けするので直しました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ 標準出力のパイプ経由でCOMインタフェースを受け渡す
  • ⇒ 標準出力のパイプ経由でCOMインタフェースを受け渡す
  • ⇒ 標準出力のパイプ経由でCOMインタフェースを受け渡す
  • ⇒ 標準出力のパイプ経由でCOMインタフェースを受け渡す