CComPtr 和 std::shared_ptr 互操作性
CComPtr and std::shared_ptr interoperability
请考虑以下场景。有一个ATL包装C++类(CMyComClass
实现IMyComClass
(,这个类用于异步操作,所以它(应该派生(从std::enable_shared_from_this
派生以提供shared_from_this()
(而不是传递this
异步函数(,这将确保在调用异步操作时对象仍然存在。另一方面,有一个COM接口,例如,它可以要求将上述类作为com对象返回,例如get(IUnknown** myobject)
或addObject(IUnkown* mynewobject)
来添加对象。在这种情况下,我陷入了僵局,我不能只是从 COM 中获取原始指针并分配给shared_ptr
,因为我没有转让所有权,并且shared_ptr
的引用计数将是错误的,因为它不计算以前的 COM 引用,此外,shared_ptr
计数的增加不会影响CComPtr
引用计数, 这意味着指针可以随时被销毁。此外,还有CMyComClass
的成员函数可以创建,例如,std::async
操作,将this
传递到其中,同样,包装 COM 可能会被破坏,我将留下悬而未决的指针。有没有办法克服这个问题?是否有相当于IUnknown
shared_from_this
.
是的,我知道,设计有缺陷,不,我现在无法更改它
EDIT001: 我想我把问题复杂化了。 让我们从根本问题开始。 考虑遵循 COM 类
class ATL_NO_VTABLE CMyComObject
: public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CMyComObject, &CLSID_MyComObject>,
public IDispatchImpl<IMyComObject, &IID_IMyComObject, &LIBID_ATLProject2Lib, /*wMajor =*/1, /*wMinor =*/0>
{
public:
CMyComObject() { m_pUnkMarshaler = NULL; }
DECLARE_REGISTRY_RESOURCEID(IDR_MYCOMOBJECT)
BEGIN_COM_MAP(CMyComObject)
COM_INTERFACE_ENTRY(IMyComObject)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
DECLARE_GET_CONTROLLING_UNKNOWN()
HRESULT FinalConstruct()
{
m_asyncTask = std::async(std::launch::async, [self{this}]() { std::cout << typeid(self).name() << std::endl; });
return CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pUnkMarshaler.p);
}
void FinalRelease() { m_pUnkMarshaler.Release(); }
CComPtr<IUnknown> m_pUnkMarshaler;
private:
std::future<void> m_asyncTask;
};
请注意FinalConstruct
中的std::async
(不是最合适的位置,但假设它是常规 COM 方法(,async
调用,计划任务,然后销毁实例(COM 对象的实例(,例如,因为实例的引用计数降至零。显然,计划任务将失败,比如说访问冲突。如何防止它发生?
EDIT002:只为卢尔兹
瞧,解决方案!
class ATL_NO_VTABLE CMyComObject
: public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CMyComObject, &CLSID_MyComObject>,
public IDispatchImpl<IMyComObject, &IID_IMyComObject, &LIBID_ATLProject2Lib, /*wMajor =*/1, /*wMinor =*/0>
{
public:
CMyComObject()
: m_pUnkMarshaler(nullptr), m_self(this, [](CMyComObject* p) {
// check if still have COM references
if(p->m_dwRef == 0)
delete p;
})
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_MYCOMOBJECT)
BEGIN_COM_MAP(CMyComObject)
COM_INTERFACE_ENTRY(IMyComObject)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
DECLARE_GET_CONTROLLING_UNKNOWN()
HRESULT FinalConstruct()
{
m_asyncTask = std::async(
std::launch::async, [self{SharedFromThis()}]() { std::cout << typeid(self).name() << std::endl; });
return CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pUnkMarshaler.p);
}
void FinalRelease() { m_pUnkMarshaler.Release(); }
ULONG InternalRelease()
{
if(m_dwRef > 0)
{
_ThreadModel::Decrement(&m_dwRef);
}
// Dont let COM delete the instance if there is shared ptrs in the wild
return m_dwRef + m_self.use_count();
}
std::shared_ptr<CMyComObject> SharedFromThis() { return m_self; }
CComPtr<IUnknown> m_pUnkMarshaler;
private:
std::future<void> m_asyncTask;
std::shared_ptr<CMyComObject> m_self;
};
这将是很好和优雅的解决方案,效果很好。但是,类本身包含对对象的一个引用,因此std::shared_ptr
的删除器永远不会启动。唉!做正确的事情,从 COM 类中获取内容,然后将提取的内容保留为 COM 类中的shared_ptr
。
正如其他人所提到的,COM 对跨线程使用接口有非常严格的规则,而不遵守这些规则只会招致错误。这是许多 COM 开发人员将其核心逻辑创建为C++类,然后将这些C++类包装在瘦 COM 对象中的众多原因之一。这是我的建议。核心对象不应具有 COM 感知的内容。如果它当前具有作为其他 COM 接口指针的数据成员,请执行相同的操作 - 将该子对象提取到 C++ 类中,并为父对象的 COM 包装器拥有的子对象提供 COM 包装器。
class CMyObject : std::enable_shared_from_this
{
public:
void Startup()
{
// non-COM stuff
auto self = shared_from_this();
m_asyncTask = std::async(std::launch::async, [self]() {
std::cout << typeid(self).name() << std::endl;
});
}
private:
std::future<void> m_asyncTask;
}
class ATL_NO_VTABLE CMyComObject
: public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CMyComObject, &CLSID_MyComObject>,
public IDispatchImpl<IMyComObject, &IID_IMyComObject, &LIBID_ATLProject2Lib, /*wMajor =*/1, /*wMinor =*/0>
{
...
HRESULT FinalConstruct()
{
m_internal = make_shared<CMyObject>();
m_internal->Startup();
// COM stuff
return CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pUnkMarshaler.p);
}
...
private:
shared_ptr<CMyObject> m_internal;
...
}
直接的方法是不要混合C++和 COM 引用计数器,而是为 COM 接口创建一个瘦C++包装对象,该对象本身可能用于异步操作:
class t_MyComClassWrapper: std::enable_shared_from_this< t_MyComClassWrapper >
{
private: CComPtr< IMyComClass > m_p_my_class;
public: t_MyComClassWrapper(void)
: m_p_my_class(new CMyComClass)
{}
// TODO forward IMyComClass methods...
};
auto p_wrapper(::std::make_shared< t_MyComClassWrapper >());
此设计将确保在 C++ 和 COM 引用计数器都降至 0 之前不会销毁该对象。
- COM 互操作性需要强命名程序集吗?
- CComPtr 和 std::shared_ptr 互操作性
- C和C++原子之间的互操作性
- C/C++ 与 C# 的互操作性命名约定
- C和C++中类型的互操作性
- C++与Fortran的互操作性
- 关于C#与非托管C++的互操作性的持续传奇
- java和c++的互操作性
- 如何从C++和通过COM互操作性访问.NET类的属性
- OpenCL/OpenGL 互操作性纹理段错误
- Swift和Objective-C++互操作性
- Windows XP和Windows 7之间的DCOM互操作性
- 寻找C++到 WPF/Cocoa 互操作性的跨平台、端到端示例
- QML 和 C++ 映像互操作性
- GNU Fortran与C的互操作性
- linux时区互操作性
- VS 2012与VS 2010的互操作性
- 互操作性的工作原理
- C++到D的互操作性
- c#结构序列的互操作性