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

CやC++でCOMインタフェースを実装するにあたり、避けて通れないのがIUnknownの実装です。他のプログラミング言語と比べ、QueryInterfaceおよびAddRef/Releaseの実装をしなければならないのは手間ですが、前向きに考えて「実装方法を柔軟に選べる」という見方もできるのではないでしょうか。

最も単純なAddRefとRelease

というわけで、本日は最も単純なAddRefとReleaseの実装の話です。

次のコードがちゃんと動くよう、なるべく単純な関数fの実装を埋めるという問いを考えてみましょう。

#incldue <windows.h>
 
IUnknown* f()
{
  // …
}
 
int main()
{
  IUnknown* p = f();
  p->Release();
}

答えはこうです。

IUnknown* f()
{
  class Object : public IUnknown
  {
    IFACEMETHOD(QueryInterface)(REFIID riid, void** ppv) override
    {
      if (ppv == nullptr)
        return E_POINTER;
      if (riid == IID_IUnknown)
      {
        *ppv = static_cast<IUnknown*>(this);
        // AddRef();
        return S_OK;
      }
      *ppv = nullptr;
      return E_NOINTERFACE;
    }
    IFACEMETHOD_(ULONG, AddRef)() override { return 1; }
    IFACEMETHOD_(ULONG, Release)() override { return 1; }
  };
  static Object obj;
  return &obj;
}

このように、何もしないAddRefとReleaseという実装があったって良いのです。オブジェクトを使う側から見て、AddRefとReleaseが正しく動いているように見えるという点で何の問題もありません。

実際、Visual C++のATLには、オブジェクトの参照カウントを行わないIUnknownの実装として、CComObjectStack、ATL::CComObjectStackEx(アンドキュメンテッド)、CComObjectGlobalの3種のクラステンプレートが存在します。

実用例

これが使える場面としてまず思い浮かぶのは、プロセスあるいはスレッドが生きている間、ずっと生存するオブジェクトに対してです。

#include <windows.h>
 
class MessageFilter : public IMessageFilter
{
public:
  IFACEMETHOD(QueryInterface)(_In_ REFIID riid, _COM_Outptr_ void** ppv) override
  {
    if (ppv == nullptr)
      return E_POINTER;
    if (riid == IID_IUnknown)
    {
      *ppv = static_cast<IUnknown*>(this);
      return S_OK;
    }
    else if (riid == IID_IMessageFilter)
    {
      *ppv = static_cast<IMessageFilter*>(this);
      return S_OK;
    }
    *ppv = nullptr;
    return E_NOINTERFACE;
  }
  IFACEMETHOD_(ULONG, AddRef)() override { return 1; }
  IFACEMETHOD_(ULONG, Release)() override { return 1; }
 
  // IMessageFilterの各メソッドの実装
};
 
int main()
{
  if (FAILED(CoInitializeEx(nullptr,
    COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
  {
    return 1;
  }
 
  MessageFilter msgFilter;
  if (FAILED(CoRegisterMessageFilter(&msgFilter, nullptr)))
  {
    // ……
  }
 
  CoUninitialize();
}

そういえば、過去の記事Windows Animation Managerを使ってみたでも、ATL::CComObjectStackExを使っています。こちらも、使用している対象はメインスレッドとほぼ同じ長さの寿命を持つオブジェクト(少々COMインタフェースを実装したメインウィンドウのクラス)です。

まとめ

AddRefとReleaseという名前に反して、何もしないという実装だってイケてしまうのです。すべてがそうというわけではありませんが、さっきのIMessageFilterのように、たまにはこんな実装で済むオブジェクトも現れます。

なお、もう1つこんなAddRef/Releaseを使っても問題ない場面があるのですが、それはまた別の日の話にしたいと思います。


スポンサード リンク

この記事のカテゴリ

  • ⇒ 参照カウントしないIUnknown
  • ⇒ 参照カウントしないIUnknown