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で弱参照を実現する(マルチスレッド対応)。
スポンサード リンク |