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に書き間違いを直しました。

スポンサード リンク

この記事のカテゴリ

  • ⇒ ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作る
  • ⇒ ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作る
  • ⇒ ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作る