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


インタフェースをマーシャリングできるようにするには、レジストリかマニフェスト、あるいはCoRegisterPSClsid関数でプロキシ・スタブのクラスを登録しなければなりません。それらの解説はほかに譲り、今日はそのような処理を行っていないインタフェースをマーシャリングしようとするとどうなるかという話です。

早速、そういうプログラムを出します。このコードでは、マーシャリング非対応のインタフェースIHogeを作っています。IUnknownとしてマーシャリングし、QueryInterfaceでIHogeを読み出そうとしています。

#include <iostream>
#include <thread>
#include <windows.h>
#include <shlwapi.h>
 
MIDL_INTERFACE("c4230d66-43bb-42a9-9f70-f580f5b96866") IHoge : IUnknown
{
  STDMETHOD(Run)();
};
 
class Hoge : public IHoge
{
public:
  IFACEMETHOD(QueryInterface)(
    _In_ REFIID riid, _COM_Outptr_ void** ppv) override
  {
    static const QITAB qit[] = {
      {&__uuidof(IHoge), OFFSETOFCLASS(IHoge, Hoge)},
      {},
    };
    return QISearch(this, qit, riid, ppv);
  }
  IFACEMETHOD_(ULONG, AddRef)() override { return 0; }
  IFACEMETHOD_(ULONG, Release)() override { return 0; }
 
  IFACEMETHOD(Run)() override { return S_OK; }
};
 
void worker(_In_ IStream* s)
{
  if (FAILED(CoInitializeEx(nullptr,
    COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
  {
    return;
  }
 
  {
#if 1
    IUnknown* u;
    std::cout << CoGetInterfaceAndReleaseStream(
      s, IID_PPV_ARGS(&u)) << std::endl;
    IHoge* hoge;
    std::cout << u->QueryInterface(IID_PPV_ARGS(&hoge)) << std::endl;
#else
    IHoge* hoge;
    std::cout << CoGetInterfaceAndReleaseStream(
      s, IID_PPV_ARGS(&hoge)) << std::endl;
#endif
  }
  CoUninitialize();
}
 
int main()
{
  std::cout << std::hex;
  if (FAILED(CoInitializeEx(nullptr,
    COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)))
  {
    return 1;
  }
 
  {
    Hoge hoge;
 
    IStream* s;
    std::cout << CoMarshalInterThreadInterfaceInStream(
      IID_IUnknown, &hoge, &s) << std::endl;
    std::thread(worker, s).join();
  }
  CoUninitialize();
}

これの実行結果は次のとおりです。

0
0
80004002

0x80004002はE_NOINTERFACEです。つまり、困ったことにオブジェクトがインタフェースを実装していないのと区別がつきません。そのため、ヌルポインタのはずがないと想定しているところにヌルがやってきて訳が分からない、と調べてみたら、レジストリやマニフェストにミスがあったということが時たまあります。こんな経験、私だけでしょうか?

なお、以下のように、CoMarshalInterThreadInterfaceInStreamの時点で登録していないIIDを渡すと、0x80040155 (REGDB_E_IIDNOTREG)が返ってきます。

std::cout << CoMarshalInterThreadInterfaceInStream(
  __uuidof(IHoge), &hoge, &s) << std::endl;

もちろん、CoMarshalInterThreadInterfaceInStreamに限らず、他の手段でマーシャリングする場合も同じです。以下はGlobal Interface Table(ATL::CComGITPtr使用)による例です。これもE_NOINTERFACE (0x80004002)になります。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <thread>
#include <tchar.h>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
MIDL_INTERFACE("c4230d66-43bb-42a9-9f70-f580f5b96866") IHoge : IUnknown
{
  STDMETHOD(Run)();
};
 
ATL::CComGITPtr<IUnknown> g1;
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public IHoge
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(IHoge)
  END_COM_MAP()
 
  IFACEMETHOD(Run)() override { return S_OK; }
};
 
void worker()
{
  if (FAILED(CoInitializeEx(
    nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
  {
    return;
  }
 
  {
    IUnknown* u;
    std::cout << g1.CopyTo(&u) << std::endl;
    IHoge* hoge;
    std::cout << u->QueryInterface(IID_PPV_ARGS(&hoge)) << std::endl;
  }
  CoUninitialize();
}
 
int main()
{
  std::cout << std::hex;
  if (FAILED(CoInitializeEx(nullptr,
    COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)))
  {
    return 1;
  }
 
  try
  {
    ATL::CComObjectStackEx<Hoge> hoge;
    g1 = &hoge;
    std::thread t(worker);
 
    t.join();
    g1.Revoke();
  }
  catch (const ATL::CAtlException& e)
  {
    std::cout << e.m_hr << std::endl;
  }
  CoUninitialize();
}

というわけで、マーシャリング先でのQueryInterfaceでE_NOINTERFACEが返ってきたら、マーシャリングの失敗の可能性も疑いましょうという話でした。


スポンサード リンク

この記事のカテゴリ

  • ⇒ マーシャリングできない場合の動き