参考:
ATL 核心COM继承类之CComObjectRootEx及CComObjectLock
背景
继承自CComObjectRootEx的类实际上并未实现IUnknown的三个方法,那么就得手动编写这三个方法
class CPenguin :
public CComObjectRootEx<CComMultiThreadModel>,
public IBird,
public ISnappyDresser {
public:
CPengin() { ServerLock(); }
~CPenguin() { ServerUnlock(); }
BEGIN_COM_MAP(CPenguin)
COM_INTERFACE_ENTRY(IBird)
COM_INTERFACE_ENTRY(ISnappyDresser)
END_COM_MAP()
// IBird and ISnappyDresser methods...
// IUnknown methods for standalone, heap-based objects
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{ return _InternalQueryInterface(riid, ppv); }
STDMETHODIMP_(ULONG) AddRef()
{ return InternalAddRef(); }
STDMETHODIMP_(ULONG) Release() {
ULONG l = InternalRelease();
if( l == 0 ) delete this;
return l;
}
};
激活COM对象
这里所指COM对象可以暂时理解为继承自IUnknown接口的对象(这很重要),激活可以理解为创建对象,那我们的目的就是简化创建对象的步骤(即激活).
1.独立激活
//Base is the user's class that derives from CComObjectRoot and whatever
//interfaces the user wants to support on the object
template <class Base>
class CComObject :
public Base
{
public:
typedef Base _BaseClass;
CComObject(_In_opt_ void* = NULL) throw()
{
_pAtlModule->Lock();
}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
virtual ~CComObject() throw()
{
m_dwRef = -(LONG_MAX/2);
FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
_pAtlModule->Unlock();
}
//If InternalAddRef or InternalRelease is undefined then your class
//doesn't derive from CComObjectRoot
STDMETHOD_(ULONG, AddRef)()
{
return InternalAddRef();
}
STDMETHOD_(ULONG, Release)()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
//if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
STDMETHOD(QueryInterface)(
_In_ REFIID iid,
_Deref_out_ void** ppvObject) throw()
{
return _InternalQueryInterface(iid, ppvObject);
}
template <class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(
_Deref_out_ Q** pp) throw()
{
return QueryInterface(__uuidof(Q), (void**)pp);
}
static HRESULT WINAPI CreateInstance(_Deref_out_ CComObject<Base>** pp) throw();
};
那么激活对象的方式可以这样:
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
*ppv = 0;
if( pUnkOuter ) return CLASS_E_NOAGGREGATION;
// Read on for why not to use new like this!
CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
if( pobj ) {
pobj->AddRef();
HRESULT hr = pobj->QueryInterface(riid, ppv);
pobj->Release();
return hr;
}
return E_OUTOFMEMORY;
}
2.聚合激活
1
template <class Base> //Base must be derived from CComObjectRoot
class CComContainedObject :
public Base
{
public:
typedef Base _BaseClass;
CComContainedObject(_In_opt_ void* pv)
{
m_pOuterUnknown = (IUnknown*)pv;
}
#ifdef _ATL_DEBUG_INTERFACES
virtual ~CComContainedObject()
{
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(m_pOuterUnknown);
}
#endif
STDMETHOD_(ULONG, AddRef)() throw()
{
return OuterAddRef();
}
STDMETHOD_(ULONG, Release)() throw()
{
return OuterRelease();
}
STDMETHOD(QueryInterface)(
_In_ REFIID iid,
_Deref_out_ void** ppvObject) throw()
{
return OuterQueryInterface(iid, ppvObject);
}
template <class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(
_Deref_out_ Q** pp)
{
return QueryInterface(__uuidof(Q), (void**)pp);
}
//GetControllingUnknown may be virtual if the Base class has declared
//DECLARE_GET_CONTROLLING_UNKNOWN()
IUnknown* GetControllingUnknown() throw()
{
#ifdef _ATL_DEBUG_INTERFACES
IUnknown* p;
_AtlDebugInterfacesModule.AddNonAddRefThunk(m_pOuterUnknown, _T("CComContainedObject"), &p);
return p;
#else
return m_pOuterUnknown;
#endif
}
};
//contained is the user's class that derives from CComObjectRoot and whatever
//interfaces the user wants to support on the object
template <class contained>
class CComAggObject :
public IUnknown,
public CComObjectRootEx< typename contained::_ThreadModel::ThreadModelNoCS >
{
public:
typedef contained _BaseClass;
CComAggObject(_In_opt_ void* pv) :
m_contained(pv)
{
_pAtlModule->Lock();
}
HRESULT _AtlInitialConstruct()
{
HRESULT hr = m_contained._AtlInitialConstruct();
if (SUCCEEDED(hr))
{
hr = CComObjectRootEx< typename contained::_ThreadModel::ThreadModelNoCS >::_AtlInitialConstruct();
}
return hr;
}
//If you get a message that this call is ambiguous then you need to
// override it in your class and call each base class' version of this
HRESULT FinalConstruct()
{
CComObjectRootEx<contained::_ThreadModel::ThreadModelNoCS>::FinalConstruct();
return m_contained.FinalConstruct();
}
void FinalRelease()
{
CComObjectRootEx<contained::_ThreadModel::ThreadModelNoCS>::FinalRelease();
m_contained.FinalRelease();
}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
virtual ~CComAggObject()
{
m_dwRef = -(LONG_MAX/2);
FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(this);
#endif
_pAtlModule->Unlock();
}
STDMETHOD_(ULONG, AddRef)()
{
return InternalAddRef();
}
STDMETHOD_(ULONG, Release)()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
STDMETHOD(QueryInterface)(
_In_ REFIID iid,
_Deref_out_ void** ppvObject)
{
ATLASSERT(ppvObject != NULL);
if (ppvObject == NULL)
return E_POINTER;
*ppvObject = NULL;
HRESULT hRes = S_OK;
if (InlineIsEqualUnknown(iid))
{
*ppvObject = (void*)(IUnknown*)this;
AddRef();
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.AddThunk((IUnknown**)ppvObject, (LPCTSTR)contained::_GetEntries()[-1].dw, iid);
#endif // _ATL_DEBUG_INTERFACES
}
else
hRes = m_contained._InternalQueryInterface(iid, ppvObject);
return hRes;
}
template <class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(_Deref_out_ Q** pp)
{
return QueryInterface(__uuidof(Q), (void**)pp);
}
static HRESULT WINAPI CreateInstance(
_Inout_opt_ LPUNKNOWN pUnkOuter,
_Deref_out_ CComAggObject<contained>** pp)
{
ATLASSERT(pp != NULL);
if (pp == NULL)
return E_POINTER;
*pp = NULL;
HRESULT hRes = E_OUTOFMEMORY;
CComAggObject<contained>* p = NULL;
ATLTRY(p = new CComAggObject<contained>(pUnkOuter))
if (p != NULL)
{
p->SetVoid(NULL);
p->InternalFinalConstructAddRef();
hRes = p->_AtlInitialConstruct();
if (SUCCEEDED(hRes))
hRes = p->FinalConstruct();
if (SUCCEEDED(hRes))
hRes = p->_AtlFinalConstruct();
p->InternalFinalConstructRelease();
if (hRes != S_OK)
{
delete p;
p = NULL;
}
}
*pp = p;
return hRes;
}
CComContainedObject<contained> m_contained;
};
支持聚合的激活创建
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
*ppv = 0;
if( pUnkOuter ) {
CComAggObject<CPenguin>* pobj =
new CComAggObject<CPenguin>(pUnkOuter);
...
}
else {
CComObject<CPenguin>* pobj = new CComObject<CPenguin>;
...
}
}
CComPolyObject
这个类封装了CComAggObject和CComObject的逻辑,介绍了一个类的虚表
class CComPolyObject :
public IUnknown,
public CComObjectRootEx<
contained::_ThreadModel::ThreadModelNoCS> {
public:
...
CComPolyObject(void* pv) : m_contained(pv ? pv : this) {...}
...
};
上面代码可以如下改写
STDMETHODIMP
CPenguinCO::CreateInstance(IUnknown* pUnkOuter, REFIID riid,
void** ppv) {
*ppv = 0;
CComPolyObject<CPenguin>* pobj =
new CComPolyObject<CPenguin>(pUnkOuter);
...
}
对象缓存激活方式(CComObjectCached)
COM锁的知识背景:当一个对象在DLL中被创建出来时候为不影响这个对象的正常使用,不可卸载相关的DLL,通过锁的机制来防止这种问题(至于锁怎么实现,可以不管,一般都是全局引用计数)。如CComObject对象的构造和析构函数调用了Lock和Unlock方法,如果想缓存这个对象的话,即保存为全局对象
//Base is the user's class that derives from CComObjectRoot and whatever
//interfaces the user wants to support on the object
// CComObjectCached is used primarily for class factories in DLL's
// but it is useful anytime you want to cache an object
template <class Base>
class CComObjectCached :
public Base
{
public:
typedef Base _BaseClass;
CComObjectCached(_In_opt_ void* = NULL)
{
}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
virtual ~CComObjectCached()
{
m_dwRef = -(LONG_MAX/2);
FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
}
//If InternalAddRef or InternalRelease is undefined then your class
//doesn't derive from CComObjectRoot
STDMETHOD_(ULONG, AddRef)() throw()
{
ULONG l = InternalAddRef();
if (l == 2)
_pAtlModule->Lock();
return l;
}
STDMETHOD_(ULONG, Release)() throw()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
else if (l == 1)
_pAtlModule->Unlock();
return l;
}
//if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
STDMETHOD(QueryInterface)(
_In_ REFIID iid,
_Deref_out_ void** ppvObject) throw()
{
return _InternalQueryInterface(iid, ppvObject);
}
static HRESULT WINAPI CreateInstance(
_Deref_out_ CComObjectCached<Base>** pp) throw();
};
那么现在你可以如下使用这个对象,避免重复创建对象
static CComObjectCached<CPenguinCO>* g_pPenguinCO = 0;
BOOL WINAPI DllMain(HINSTANCE, DWORD dwReason, void*) {
switch( dwReason ) {
case DLL_PROCESS_ATTACH:
g_pPenguinCO = new CComObjectCached<CPenguinCO>();
// 1st ref. doesn't keep server alive
if( g_pPenguinCO ) g_pPenguinCO->AddRef();
break;
case DLL_PROCESS_DETACH:
if( g_pPenguinCO ) g_pPenguinCO->Release();
break;
}
return TRUE;
}
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,
void** ppv) {
// Subsequent references do keep server alive
if( clsid == CLSID_Penguin && g_pPenguinCO )
return g_pPenguinCO->QueryInterface(riid, ppv);
return CLASS_E_CLASSNOTAVAILABLE;
}
无锁对象激活CComObjectNoLock
上面讲过CComObjectCached是对于DLL相关的对象,如果是进程外的对象可以不用锁,不会影响程序的生命周期
1
//Base is the user's class that derives from CComObjectRoot and whatever
//interfaces the user wants to support on the object
template <class Base>
class CComObjectNoLock :
public Base
{
public:
typedef Base _BaseClass;
CComObjectNoLock(_In_opt_ void* = NULL)
{
}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
virtual ~CComObjectNoLock()
{
m_dwRef = -(LONG_MAX/2);
FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
}
//If InternalAddRef or InternalRelease is undefined then your class
//doesn't derive from CComObjectRoot
STDMETHOD_(ULONG, AddRef)() throw()
{
return InternalAddRef();
}
STDMETHOD_(ULONG, Release)() throw()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
//if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
STDMETHOD(QueryInterface)(
_In_ REFIID iid,
_Deref_out_ void** ppvObject) throw()
{
return _InternalQueryInterface(iid, ppvObject);
}
};
如下调用:
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
CoInitialize(0);
CComObjectNoLock<CPenguinCO>* pPenguinCO =
new CComObjectNoLock<CPenguinCO>();
if( !pPenguinCO ) return E_OUTOFMEMORY;
pPenguinCO->AddRef();
DWORD dwReg;
HRESULT hr;
// Reference(s) cached by ole32.dll won't keep server
// from shutting down
hr = CoRegisterClassObject(CLSID_Penguin, pPenguinCO, ...,
&dwReg);
if( SUCCEEDED(hr) ) {
MSG msg; while( GetMessage(&msg, 0, 0, 0) ) DispatchMessage(&msg);
CoRevokeClassObject(dwReg);
pPenguinCO->Release();
}
CoUninitialize();
return hr;
}
全局对象激活CComObjectGlobal
这个对象激活了将于服务器周期一样,即结束后才释放(因为其本身就没释放过)
// It is possible for Base not to derive from CComObjectRoot
// However, you will need to provide _InternalQueryInterface
template <class Base>
class CComObjectGlobal :
public Base
{
public:
typedef Base _BaseClass;
CComObjectGlobal(_In_opt_ void* = NULL)
{
m_hResFinalConstruct = S_OK;
__if_exists(FinalConstruct)
{
__if_exists(InternalFinalConstructAddRef)
{
InternalFinalConstructAddRef();
}
m_hResFinalConstruct = _AtlInitialConstruct();
if (SUCCEEDED(m_hResFinalConstruct))
m_hResFinalConstruct = FinalConstruct();
__if_exists(InternalFinalConstructRelease)
{
InternalFinalConstructRelease();
}
}
}
virtual ~CComObjectGlobal()
{
__if_exists(FinalRelease)
{
FinalRelease();
}
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
}
STDMETHOD_(ULONG, AddRef)() throw()
{
return _pAtlModule->Lock();
}
STDMETHOD_(ULONG, Release)() throw()
{
return _pAtlModule->Unlock();
}
STDMETHOD(QueryInterface)(
_In_ REFIID iid,
_Deref_out_ void** ppvObject) throw()
{
return _InternalQueryInterface(iid, ppvObject);
}
HRESULT m_hResFinalConstruct;
};
在栈上创建对象
// No references yet, so server not forced to stay alive
static CComObjectGlobal<CPenguinCO> g_penguinCO;
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,
void** ppv) {
// All references keep the server alive
if( clsid == CLSID_Penguin )
return g_penguinCO.QueryInterface(riid, ppv);
return CLASS_E_CLASSNOTAVAILABLE;
}