アパートメントを越えて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*があるためマーシャリングが必要という扱いにしています。
スポンサード リンク |