アパートメントを越えてVARIANTを持ち運びするためのクラスを書きました。名前はFreeThreadedVariantです。インタフェースへのポインタにおけるATL::CComGITPtr(リンク先:MSDNライブラリ)のように使えるものが欲しくて、これを作りました。

こんなコードが書けるようになります。

std::cout << "マーシャリング不要な型" << std::endl;
auto f1 = std::async(std::launch::async, []
{
  CoInitilaizer coInit;
  return FreeThreadedVariant(_variant_t(1));
});
std::cout << static_cast<int>(f1.get().Get()) << std::endl;
// GetはFreeThreadedVariantのメンバ関数
 
std::cout << "マーシャリングが行われる場合" << std::endl;
auto f2 = std::async(std::launch::async, []
{
  CoInitilaizer coInit;
  IUnknownPtr p(CLSID_ShellWindows);
  std::cout << p.GetInterfacePtr() << std::endl;
  return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
});
std::cout << static_cast<IUnknown*>(f2.get().Get()) << std::endl;
 
std::cout << "マーシャリングが行われない場合" << std::endl;
auto f3 = std::async(std::launch::async, []
{
  CoInitilaizer coInit;
  IUnknownPtr p(__uuidof(FreeThreadedDOMDocument60));
  std::cout << p.GetInterfacePtr() << std::endl;
  return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
});
std::cout << static_cast<IUnknown*>(f3.get().Get()) << std::endl;

実装は簡単でした。VT_I4など、マーシャリング不要な型と、COMインタフェースを含む型(VT_UNKNOWN, VT_DISPATCH, VT_RECORD)に分けます。マーシャリングが必要な型については、別のCOMオブジェクトでラップして、そこから取り出すという形にします。ラップしたオブジェクトからの取り出しをプロパティ(IDispatch.Invoke)にして、そこでマーシャリングを実行させています。

詳しくは以下のソースコード全文を読んでみてください。

#include <iostream>
#include <future>
#include <utility> // for std::swap
#include <exdisp.h>
#include <msxml6.h>
#include <atlbase.h>
#include <atlcom.h>
#include <comdef.h>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
// ここから
// https://dev.activebasic.com/egtra/2014/02/08/640/
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)
    {
      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
 
// https://dev.activebasic.com/egtra/2014/02/08/640/
// ここまで
 
class ATL_NO_VTABLE VariantHolder
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModelNoCS>
  , public IDispatch
{
  friend class FreeThreadedVariant;
 
  BEGIN_COM_MAP(VariantHolder)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()
 
public:
  VariantHolder(const _variant_t& v)
  {
    ATLENSURE_SUCCEEDED(::VariantCopyInd(&m_value, &v));
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
    /* [out] */ __RPC__out UINT*) override
  {
    return E_NOTIMPL;
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
    /* [in] */ UINT,
    /* [in] */ LCID,
    /* [out] */ ITypeInfo**) override
  {
    return E_NOTIMPL;
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
    /* [in] */ REFIID,
    /* [size_is][in] */ LPOLESTR*,
    /* [range][in] */UINT,
    /* [in] */ LCID ,
    /* [size_is][out] */DISPID*) override
  {
    return E_NOTIMPL;
  }
 
  virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
    /* [annotation][in] */
    _In_ DISPID dispIdMember,
    /* [annotation][in] */
    _In_ REFIID riid,
    /* [annotation][in] */
    _In_ LCID,
    /* [annotation][in] */
    _In_ WORD wFlags,
    _In_ DISPPARAMS *pDispParams,
    _Out_opt_ VARIANT*pVarResult,
    _Out_opt_ EXCEPINFO*,
    _Out_opt_ UINT*) override
  {
    if (dispIdMember != DISPID_VALUE || wFlags != DISPATCH_PROPERTYGET)
    {
      return DISP_E_MEMBERNOTFOUND;
    }
    if (riid != IID_NULL)
    {
      return DISP_E_UNKNOWNINTERFACE;
    }
    if (pDispParams->cArgs != 0)
    {
      return DISP_E_BADPARAMCOUNT;
    }
    if (pDispParams->cNamedArgs != 0)
    {
      return DISP_E_NONAMEDARGS;
    }
    VariantInit(pVarResult);
    return VariantCopy(pVarResult, &m_value);
  }
 
private:
  _variant_t m_value;
 
  VariantHolder(const VariantHolder&) = delete;
  VariantHolder& operator=(const VariantHolder&) = delete;
};
 
class FreeThreadedVariant
{
public:
  FreeThreadedVariant() = default;
  FreeThreadedVariant(FreeThreadedVariant&& x)
  {
    std::swap(m_value.GetVARIANT(), x.m_value.GetVARIANT());
    m_getter = std::move(x.m_getter);
  }
  FreeThreadedVariant(const FreeThreadedVariant&) = default;
  FreeThreadedVariant(const VARIANT& value) { Set(value); }
 
  FreeThreadedVariant& operator=(FreeThreadedVariant&& x)
  {
    std::swap(m_value.GetVARIANT(), x.m_value.GetVARIANT());
    m_getter = std::move(x.m_getter);
    return *this;
  }
  FreeThreadedVariant& operator=(const FreeThreadedVariant&) = default;
  FreeThreadedVariant& operator=(const VARIANT& value)
  {
    Set(value);
    return *this;
  }
 
  ~FreeThreadedVariant() = default;
 
  operator _variant_t() const
  {
    return Get();
  }
 
  void Set(const VARIANT& value)
  {
    if (IsNeedToMarshal(value))
    {
      m_getter = egtra::CreateComObject<VariantHolder>(value);
    }
    else
    {
      ATLENSURE_SUCCEEDED(VariantCopyInd(&m_value, &value));
    }
  }
 
  _variant_t Get() const
  {
    if (!m_getter)
    {
      return m_value;
    }
    ATL::CComPtr<IDispatch> obj;
    ATLENSURE_SUCCEEDED(m_getter.CopyTo(&obj));
    _variant_t ret;
    ATLENSURE_SUCCEEDED(obj.GetProperty(DISPID_VALUE, &ret));
    return ret;
  }
 
private:
  static bool IsNeedToMarshal(const VARIANT& v)
  {
    switch (v.vt & ~(VT_ARRAY | VT_BYREF))
    {
    case VT_EMPTY:
    case VT_NULL:
    case VT_I1:
    case VT_I2:
    case VT_I4:
    case VT_I8:
    case VT_R4:
    case VT_R8:
    case VT_CY:
    case VT_DATE:
    case VT_BSTR:
    case VT_ERROR:
    case VT_BOOL:
    case VT_DECIMAL:
    case VT_UI1:
    case VT_UI2:
    case VT_UI4:
    case VT_UI8:
    case VT_INT:
    case VT_UINT:
      return false;
    default:
      // SAFEARRAYの場合の判定をサボっている。
      if (v.vt == VT_UNKNOWN || v.vt == VT_DISPATCH)
      {
        return ATL::CComQIPtr<IAgileObject>(v.punkVal) == nullptr;
      }
      else
      {
        return true;
      }
    }
  }
 
  _variant_t m_value;
  ATL::CComGITPtr<IDispatch> m_getter;
};
 
struct CoInitilaizer
{
  CoInitilaizer()
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
  }
  ~CoInitilaizer()
  {
    CoUninitialize();
  }
private:
  CoInitilaizer(const CoInitilaizer&) = delete;
  CoInitilaizer& operator =(const CoInitilaizer&) = delete;
};
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
    CO_MTA_USAGE_COOKIE mtaCookie = {};
    ATLENSURE_SUCCEEDED(CoIncrementMTAUsage(&mtaCookie));
 
    std::cout << "マーシャリング不要な型" << std::endl;
    auto f1 = std::async(std::launch::async, []
    {
      CoInitilaizer coInit;
      return FreeThreadedVariant(_variant_t(1));
    });
    std::cout << static_cast<int>(f1.get().Get()) << std::endl;
 
    std::cout << "マーシャリングが行われる場合" << std::endl;
    auto f2 = std::async(std::launch::async, []
    {
      CoInitilaizer coInit;
      IUnknownPtr p(CLSID_ShellWindows);
      std::cout << p.GetInterfacePtr() << std::endl;
      return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
    });
    std::cout << static_cast<IUnknown*>(f2.get().Get()) << std::endl;
 
    std::cout << "マーシャリングが行われない場合" << std::endl;
    auto f3 = std::async(std::launch::async, []
    {
      CoInitilaizer coInit;
      IUnknownPtr p(__uuidof(FreeThreadedDOMDocument60));
      std::cout << p.GetInterfacePtr() << std::endl;
      return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
    });
    std::cout << static_cast<IUnknown*>(f3.get().Get()) << std::endl;
 
    ATLENSURE_SUCCEEDED(CoDecrementMTAUsage(mtaCookie));
  }
  catch (const ATL::CAtlException& e)
  {
    std::cerr << "ATL::CAtlException" << std::hex << e.m_hr << std::endl;
  }
  catch (const std::exception& e)
  {
    std::cerr << "std::exception" << e.what() << std::endl;
  }
  catch (const _com_error& e)
  {
    std::cerr << "_com_error" << std::hex << e.Error() << std::endl;
  }
  CoUninitialize();
}
  • ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作るのコードを利用しています。
  • IAgileObjectはマーシャリング不要であることを示すインタフェースです。XPターゲットだと宣言されませんので、その場合は自分でソースコードに宣言を書いてしまいましょう。

    MIDL_INTERFACE("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")
    IAgileObject : IUnknown {};
  • VT_ARRAYかつVT_UNKNOWN, VT_DISPATCHの場合、すべての要素がIAgileObjectをQueryIterface
    できるかどうか調べるべきです。しかし、VT_ARRAYをそんなによく使うわけではないので今回はサボっています。
  • VT_ARRAY | VT_VARIANTのときも、マーシャリング不要な場合があるはずですが、同様にサボっています。
  • VT_RECORDは、IRecordInfo*があるためマーシャリングが必要という扱いにしています。

スポンサード リンク

この記事のカテゴリ

  • ⇒ アパートメント(スレッド)を越えても安全に持ち運びできるVARIANTラッパーを作った
  • ⇒ アパートメント(スレッド)を越えても安全に持ち運びできるVARIANTラッパーを作った
  • ⇒ アパートメント(スレッド)を越えても安全に持ち運びできるVARIANTラッパーを作った