WinRTには弱参照のCOMインタフェースがあります。WinRTに限らず使いたいのですが、あいにくWinRT専用の作りだったので、代替品を自作しました。

これと同名のインタフェースとメソッド(ただし引数の型が少し異なるもの)を作ったわけです。そして、それに対する実装も作りました。

インタフェース

弱参照を使用できるオブジェクトはIWeakReferenceSourceを実装します。

MIDL_INTERFACE("de2988fe-a6b7-4e3d-923e-7463ce0e1040")
IWeakReferenceSource : IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
    /* [retval][out] */ __RPC__deref_out IWeakReference** weakReference) = 0;
};

GetWeakReferenceメソッドで弱参照を取得できるというわけです。

IWeakReferenceインタフェースが弱参照を表すインタフェースです。
こちらは、強参照のオブジェクトを取得するResolveメソッドを持っています。

MIDL_INTERFACE("bdcb7ca6-376d-481a-8652-dfd69f723ecc")
IWeakReference : IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE Resolve(
    /* [in] */ __RPC__in REFIID riid,
    /* [iid_is][out] */ __RPC__deref_out void** objectReference) = 0;
};

対象のオブジェクトがすでに存在しない場合、成功値を返しますが*objectReferenceにはnullptrが格納されます。その場合の戻り値はWinRTだとS_OKでしたが、私の好みはS_FALSEです。

WinRT APIでは2番目の引数がIInspectable**でしたので、非WinRTなCOMインタフェースでは使用不可能でした。そこで、こちらはvoid**にしています。そのため、上記のIWeakReferenceSourceとIWeakReferenceはWinRTのものと異なるIIDにしています。

使用例

このインタフェースを実装するクラス、WeakReferenceImplとWeakReferenceSourceImpl、オブジェクト作成関数CreateComObjectWithWeakRefを作りました。今回はシングルスレッド専用です。

int main()
{
  ATLENSURE_RETURN_VAL(SUCCEEDED(CoInitializeEx(nullptr, COINIT::COINIT_APARTMENTTHREADED)), 1);
 
  {
    ATL::CComPtr<egtra::IWeakReference> weak;
    {
      auto x = CreateComObjectWithWeakRef<TestImpl>();
      ATL::CComQIPtr<egtra::IWeakReferenceSource> s(x);
      ATLENSURE_SUCCEEDED(s->GetWeakReference(&weak));
 
      ATL::CComPtr<IUnknown> u;
      weak->Resolve(IID_PPV_ARGS(&u));
      std::cout << implicit_cast<IUnknown*>(u) << std::endl;
    }
 
    ATL::CComPtr<IUnknown> u;
    weak->Resolve(IID_PPV_ARGS(&u));
    std::cout << implicit_cast<IUnknown*>(u) << std::endl;
  }
 
  CoUninitialize();
  return 0;
}

これを実行すると、1つ目のuは非ヌルポインタである値が出力され、2つ目のuはヌルポインタを取得していることが分かります。

ソースコード

最後に、ソースコード全体です。シングルスレッド限定のため、弱参照の実現方法は単純です。

  • FinalConstructで弱参照用に生のポインタをセット
  • FinalReleaseで弱参照から辿れないようnullptrをセット
#include <iostream>
#include <memory>
#include <intrin.h>
#include <atlbase.h>
#include <atlcom.h>
#include <boost/implicit_cast.hpp>
 
using boost::implicit_cast;
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
namespace egtra
{
  MIDL_INTERFACE("bdcb7ca6-376d-481a-8652-dfd69f723ecc")
  IWeakReference : IUnknown
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE Resolve(
      /* [in] */ __RPC__in REFIID riid,
      /* [iid_is][out] */ __RPC__deref_out void** objectReference) = 0;
  };
 
  MIDL_INTERFACE("de2988fe-a6b7-4e3d-923e-7463ce0e1040")
  IWeakReferenceSource : IUnknown
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
      /* [retval][out] */ __RPC__deref_out IWeakReference** weakReference) = 0;
  };
}
 
template<typename T>
ATL::CComPtr<T> CreateComObject()
{
  auto p = std::make_unique<ATL::CComObject<T>>();
  p->SetVoid(nullptr);
  p->InternalFinalConstructAddRef();
  HRESULT hRes = p->_AtlInitialConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->FinalConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->_AtlFinalConstruct();
  p->InternalFinalConstructRelease();
  return hRes == S_OK
    ? p.release()
    : nullptr;
}
 
class WeakReferenceImpl
  : public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public egtra::IWeakReference
{
  BEGIN_COM_MAP(WeakReferenceImpl)
    COM_INTERFACE_ENTRY(egtra::IWeakReference)
  END_COM_MAP()
 
public:
  virtual HRESULT STDMETHODCALLTYPE Resolve(
    /* [in] */ _In_ REFIID riid,
    /* [iid_is][out] */ _COM_Outptr_result_maybenull_ void** ppv) throw() override
  {
    ATLENSURE_RETURN_HR(ppv != nullptr, E_POINTER);
    *ppv = nullptr;
    if (m_obj == nullptr)
    {
      return S_FALSE;
    }
    return m_obj->QueryInterface(riid, ppv);
  }
 
  void SetObject(IUnknown* obj)
  {
    m_obj = obj;
  }
 
private:
  IUnknown* m_obj = nullptr;
};
 
template<class T>
class WeakReferenceSourceImpl
  : public T
  , public egtra::IWeakReferenceSource
{
  static_assert(
    std::is_same<typename T::_ThreadModel, ATL::CComSingleThreadModel>::value,
    "Only ATL::CComSingleThreadModel is supported.");
 
  BEGIN_COM_MAP(WeakReferenceSourceImpl)
    COM_INTERFACE_ENTRY(egtra::IWeakReferenceSource)
    COM_INTERFACE_ENTRY_CHAIN(T)
  END_COM_MAP()
 
public:
  virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
    /* [retval][out] */ _COM_Outptr_ egtra::IWeakReference** weakReference) throw() override
  {
    ATLENSURE_RETURN_HR(weakReference != nullptr, E_POINTER);
    *weakReference = m_weakRef;
    (*weakReference)->AddRef();
    return S_OK;
  }
 
  HRESULT FinalConstruct()
  {
    // このアップキャストはGetControllingUnknown()の代わり
    m_weakRef->SetObject(implicit_cast<egtra::IWeakReferenceSource*>(this));
    return T::FinalConstruct();
  }
 
  void FinalRelease()
  {
    m_weakRef->SetObject(nullptr);
    T::FinalRelease();
  }
 
private:
  ATL::CComPtr<WeakReferenceImpl> m_weakRef
    = CreateComObject<WeakReferenceImpl>();
};
 
template<typename T>
ATL::CComPtr<T> CreateComObjectWithWeakRef()
{
  return implicit_cast<T*>(CreateComObject<WeakReferenceSourceImpl<T>>());
}
 
MIDL_INTERFACE("0f9a78ce-4f58-4160-9889-e3bd4485c92d") ITest : IUnknown
{
  virtual HRESULT STDMETHODCALLTYPE Test() = 0;
};
 
class ATL_NO_VTABLE TestImpl
  : public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public ITest
{
  DECLARE_NOT_AGGREGATABLE(TestImpl)
 
  BEGIN_COM_MAP(TestImpl)
    COM_INTERFACE_ENTRY(ITest)
  END_COM_MAP()
 
public:
  HRESULT FinalConstruct() throw()
  {
    std::cout << "TestImpl::FinalConstruct" << std::endl;
    return S_OK;
  }
 
  HRESULT FinalRelease() throw()
  {
    std::cout << "TestImpl::FinalRelease" << std::endl;
    return S_OK;
  }
 
  virtual HRESULT STDMETHODCALLTYPE Test() throw() override
  {
    std::cout << "TestImpl::Test" << std::endl;
    return S_OK;
  }
 
  void Test2()
  {
    std::cout << "TestImpl::Test2 (non-virtual)" << std::endl;
  }
};
 
int main()
{
  ATL::CComPtr<egtra::IWeakReference> weak;
  {
    auto x = CreateComObjectWithWeakRef<TestImpl>();
    ATL::CComQIPtr<egtra::IWeakReferenceSource> s(x);
    ATLENSURE_SUCCEEDED(s->GetWeakReference(&weak));
 
    ATL::CComPtr<IUnknown> u;
    weak->Resolve(IID_PPV_ARGS(&u));
    std::cout << implicit_cast<IUnknown*>(u) << std::endl;
  }
 
  ATL::CComPtr<IUnknown> u;
  weak->Resolve(IID_PPV_ARGS(&u));
  std::cout << implicit_cast<IUnknown*>(u) << std::endl;
}

次回はマルチスレッド対応版の予定です。→書きました:COMで弱参照を実現する(マルチスレッド対応)


スポンサード リンク

この記事のカテゴリ

  • ⇒ COMで弱参照を実現する
  • ⇒ COMで弱参照を実現する
  • ⇒ COMで弱参照を実現する