Consider following scenario. There is a ATL wrapped C++ class (CMyComClass which implements IMyComClass), this class is used in async operations so it (should derive) derives from std::enable_shared_from_this to provide shared_from_this() (instead of passing this to async function) which will ensure that when the async operation is invoked the object still exists. On the other hand there is a COM interface which, for example can ask for the above class to be returned as com object, something like get(IUnknown** myobject) or addObject(IUnkown* mynewobject) to add the object. At this situation I'm in a deadlock, I cant just take raw pointer from the COM and assign to the shared_ptr since I do not transfer the ownership, and the reference counting of shared_ptr will be wrong since it doesnt count previous COM references, in addition and increase in shared_ptr count will not affect the CComPtr ref count, meaning the pointer could be destroyed any time. In addition there are member functions of CMyComClass which could create, for example, std::async operation, passing this into it, again, the wrapping COM could be destroyed and I will be left with dangling pointer. Is there a way to overcome this problem? Is there an equivalent of shared_from_this on IUnknown.
Yes, I know, the design is flawed and no, I cant change it now
EDIT001:
I think I overcomplicated the question.
Lets start from the root problem.
Consider following COM class
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;
};
Note the std::async in the FinalConstruct (not most suitable place, but pretend it is a regular COM method), async is called, task is scheduled, then the instance (instance of COM object) is destroyed, for example because reference count of the instance dropped to zero. Obviously the scheduled task will fail, lets say with access violation. How one would prevent it from happening?
EDIT002: just for lulz
Voila, the solution!
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;
};
It would be nice and elegant solution which works great. However, the class itself holding one reference to the object, so the deleter of the std::shared_ptr will never kick in. Alas! Do the right thing and get the stuff out of COM class and then keep the extracted stuff as shared_ptr in the COM class.
enable_shared_from_thisthe instance must be created by, for example,std::make_shared. however, since it is a COM class it could be (and most likely will be) created byCreateInstanceornew ActiveXObject. How theenable_shared_from_thiswould work in this case? - kreuzerkrieg