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


マーシャリングされたオブジェクトの寿命は、アパートメントに縛られます。アパートメントが終わったら、そのアパートメントにいるオブジェクトにはアクセスできなくなります。

今度のプログラムは、STAのスレッドを(1) TerminateThread (2) CoUninitializeで終了させて、オブジェクトが使えなくなることを見てみます。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <future>
#include <thread>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public ATL::CComCoClass<Hoge>
  , public ISequentialStream
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(ISequentialStream)
  END_COM_MAP()
 
  IFACEMETHOD(Read)(
    _Out_writes_bytes_to_(cb, *pcbRead) void* pv,
    _In_ ULONG cb,
    _Out_opt_ ULONG *pcbRead) override
  {
    if (pcbRead == nullptr)
      return E_POINTER;
    *pcbRead = 0;
    return S_OK;
  }
 
  IFACEMETHOD(Write)(
    _In_reads_bytes_(cb) const void* pv,
    _In_ ULONG cb,
    _Out_opt_ ULONG *pcbWritten) override
  {
    return E_NOTIMPL;
  }
};
 
std::promise<ATL::CComGITPtr<ISequentialStream>> p;
 
void worker()
{
  if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
    return;
 
  try
  {
    ATL::CComPtr<ISequentialStream> u;
    Hoge::CreateInstance(&u);
    ATL::CComGITPtr<ISequentialStream> g = u;
    p.set_value(std::move(g));
 
    MSG msg;
    for (;;)
    {
      int ret = GetMessage(&msg, nullptr, 0, 0);
      if (ret == 0 || ret == -1)
        break;
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << ATL::AtlGetErrorDescription(e) << std::endl;
  }
  CoUninitialize();
}
 
int main()
{
  if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)))
    return 1;
 
  try
  {
    auto f = p.get_future();
    std::thread t(worker);
    auto g = f.get();
    ATL::CComPtr<ISequentialStream> s;
    ATLENSURE_SUCCEEDED(g.CopyTo(&s));
 
#if 1
    // (1)
    TerminateThread(t.native_handle(), 1);
    t.join();
#else
    // (2)
    PostThreadMessage(GetThreadId(t.native_handle()), WM_QUIT, 0, 0);
    t.join();
#endif
 
    std::cout << std::hex;
    char c;
    ULONG read;
    HRESULT hr = s->Read(&c, sizeof c, &read);
    std::cout << "" << hr << ' ' << ATL::AtlGetErrorDescription(hr) << std::endl;
  }
  catch (const std::exception& e)
  {
    std::clog << e.what() << std::endl;
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << ATL::AtlGetErrorDescription(e) << std::endl;
  }
  CoUninitialize();
  return 0;
}

関数workerからISequentialStreamを受け取り、それに対するReadをスレッドの終了より後に実行されるようにしています。そのエラーコードを出力させているのが今回の目的です。

まず、TerminateThreadのほうで実行すると、こうなります。

80010100 システム コールに失敗しました。

#if 0に書き換えて、CoUninitializeで自発的に終了するようにすると次のようになります。

80010012 システム コールに失敗しました。

これはWindows 8.1での結果です。OSなどの違いによっては異なる結果になる可能性があります。

ちなみに、WinError.hを見ると定数名が分かります。0x80010100はRPC_E_SYS_CALL_FAILED、0x80010012はRPC_E_SERVER_DIED_DNEです。


同一アパートメントでは無理ですが、アパートメントをまたぐとこのようにオブジェクトが死んだことを検知できます。そういうとき、このように0x8001xxxx系(FACILITY_RPC)のエラーコードが返ってきます。

同一プロセス内のスレッドが死ぬことはあまり想定しないかもしれませんが、相手が別プロセスとなると十分起こりえる事態です(ユーザーがタスクマネージャーなどで無差別にプロセスを強制終了できるわけですから)。


スポンサード リンク

この記事のカテゴリ

  • ⇒ アパートメントが死ぬときオブジェクトも死ぬ