ATLでCOMオブジェクトを作る話の続きです。今回で一応終わります。
ATLでCOMインタフェースを実装するC++クラスを作っていて遭遇した、次なる壁は引数を持つコンストラクタでした。
ATL::CComObjectRootExから派生したクラスで、引数を取るコンストラクタを持たせたいと思いました。しかし、そんなことができるようにはなっていません。
これがATL::CComObjectのコンストラクタです。基底クラス(CComObjectRoot(Ex)から派生した自作クラス)にコンストラクタ引数を渡す余地はありません。
// CComObject内 CComObject(_In_opt_ void* = NULL) { _pAtlModule->Lock(); } |
仕方がないのでATL::CComObjectに代わるクラスを自作してしまいました。それが今回の内容です。
変更点はコンストラクタを以下のように変えたこと、それだけです(実際には、CreateInstanceやテンプレート版QueryInterfaceなど一部を面倒なので除外しています)。
// 自作CComObject template<typename... Args> CComObject(Args&&... args) : Base(std::forward<Args>(args)...) {} |
以下コード全体です。前回作ったCreateComObject関数も可変長引数に修正してあります。
#define _ATL_NO_AUTOMATIC_NAMESPACE #include <iostream> #include <memory> #include <atlbase.h> #include <atlcom.h> MIDL_INTERFACE("0f9a78ce-4f58-4160-9889-e3bd4485c92d") ITest : IUnknown { virtual HRESULT STDMETHODCALLTYPE Test() = 0; }; class ATL_NO_VTABLE TestImpl : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel> , public ITest { DECLARE_NOT_AGGREGATABLE(TestImpl) BEGIN_COM_MAP(TestImpl) COM_INTERFACE_ENTRY(ITest) END_COM_MAP() public: explicit TestImpl(std::unique_ptr<int> p) : p(std::move(p)) {} 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 *p = " << *p << std::endl; return S_OK; } void Test2() { std::cout << "TestImpl::Test2 (non-virtual)" << std::endl; } private: std::unique_ptr<int> p; }; namespace egtra { template <class Base> class CComObject : public Base { public: typedef Base _BaseClass; template<typename... Args> CComObject(Args&&... args) : Base(std::forward<Args>(args)...) {} virtual ~CComObject() { m_dwRef = -(LONG_MAX/2); FinalRelease(); #if defined(_ATL_DEBUG_INTERFACES) && !defined(_ATL_STATIC_LIB_IMPL) ATL::_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown()); #endif ATL::_pAtlModule->Unlock(); } STDMETHOD_(ULONG, AddRef)() { return InternalAddRef(); } STDMETHOD_(ULONG, Release)() { ULONG l = InternalRelease(); if (l == 0) { ATL::ModuleLockHelper lock; delete this; } return l; } STDMETHOD(QueryInterface)(REFIID iid, _COM_Outptr_ void** ppvObject) throw() { return _InternalQueryInterface(iid, ppvObject); } }; template<typename T, typename... Args> ATL::CComPtr<T> CreateComObject(Args&&... args) { std::unique_ptr<CComObject<T>> p( new CComObject<T>(std::forward<Args>(args)...)); 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; } } // namespace egtra class Module : public ATL::CAtlExeModuleT<Module> {}; Module module; int main() { using namespace egtra; try { auto impl = CreateComObject<TestImpl>(std::make_unique<int>(1000)); impl->Test2(); ATL::CComPtr<ITest> test(impl); test->Test(); } catch (const ATL::CAtlException& e) { std::cerr << std::hex << std::showbase << "Failed: " << e << std::endl; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } |
CreateComObjectの中でstd::make_uniqueを使っていないのは、過去のバージョンのVisual C++で使うことを一応考えているためです。もっとも、その場合には可変長引数にも対応していない(make_uniqueとともにVisual C++ 2013からです)ので、まずはそっちの対処を考えなければならないのですが。
なお、コンストラクタの時点ではAddRef/Release/QueryInterfaceは使用できません。ATLのCComObjectなどでも同じです。注意してください。IUnknownとしての操作が必要なら、DECLARE_PROTECT_FINAL_CONSTRUCTを定義して、FinalConstruct関数で書きましょう。
なんだか、だんだんATLに頼らないでコードを書くという結果になってしまいました。
2014年2月16日追記:CraeteComObjectからCreateComObjectに書き間違いを直しました。
スポンサード リンク |