在ATL中,类厂是计数的。1. CComCoClass
template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass
{
public:
DECLARE_CLASSFACTORY()...
}2.
#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)3. 类厂的_ClassFactoryCreatorClass
#if defined(_WINDLL) | defined(_USRDLL)
#define DECLARE_CLASSFACTORY_EX(cf) typedef ATL::CComCreator< ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;
#else
// don't let class factory refcount influence lock count
#define DECLARE_CLASSFACTORY_EX(cf) typedef ATL::CComCreator< ATL::CComObjectNoLock< cf > > _ClassFactoryCreatorClass;
#endif4.1 对于in-pro
template <class Base>
class CComObjectCached : public Base
{
public:
typedef Base _BaseClass;
CComObjectCached(void* = NULL){}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
~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)()
{
m_csCached.Lock();
ULONG l = InternalAddRef();
if (m_dwRef == 2)
_pAtlModule->Lock();
m_csCached.Unlock();
return l;
}
STDMETHOD_(ULONG, Release)()
{
m_csCached.Lock();
InternalRelease();
ULONG l = m_dwRef;
m_csCached.Unlock();
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)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
CComGlobalsThreadModel::AutoCriticalSection m_csCached;
};4.2 对于local server or service
template <class Base>
class CComObjectNoLock : public Base
{
public:
typedef Base _BaseClass;
CComObjectNoLock(void* = NULL){}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
~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)() {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)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
};由此可见,在ATL中类厂是计数的。
只不过对于in-pro, 只有当m_dwRef由1->2时,才lock server;当m_dwRef由2->1时,就unlock server;通过这种手段来避免“自相矛盾”。而对于local server or service, 计数但不lock/unlock server.
template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass
{
public:
DECLARE_CLASSFACTORY()...
}2.
#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)3. 类厂的_ClassFactoryCreatorClass
#if defined(_WINDLL) | defined(_USRDLL)
#define DECLARE_CLASSFACTORY_EX(cf) typedef ATL::CComCreator< ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;
#else
// don't let class factory refcount influence lock count
#define DECLARE_CLASSFACTORY_EX(cf) typedef ATL::CComCreator< ATL::CComObjectNoLock< cf > > _ClassFactoryCreatorClass;
#endif4.1 对于in-pro
template <class Base>
class CComObjectCached : public Base
{
public:
typedef Base _BaseClass;
CComObjectCached(void* = NULL){}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
~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)()
{
m_csCached.Lock();
ULONG l = InternalAddRef();
if (m_dwRef == 2)
_pAtlModule->Lock();
m_csCached.Unlock();
return l;
}
STDMETHOD_(ULONG, Release)()
{
m_csCached.Lock();
InternalRelease();
ULONG l = m_dwRef;
m_csCached.Unlock();
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)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
CComGlobalsThreadModel::AutoCriticalSection m_csCached;
};4.2 对于local server or service
template <class Base>
class CComObjectNoLock : public Base
{
public:
typedef Base _BaseClass;
CComObjectNoLock(void* = NULL){}
// Set refcount to -(LONG_MAX/2) to protect destruction and
// also catch mismatched Release in debug builds
~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)() {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)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
};由此可见,在ATL中类厂是计数的。
只不过对于in-pro, 只有当m_dwRef由1->2时,才lock server;当m_dwRef由2->1时,就unlock server;通过这种手段来避免“自相矛盾”。而对于local server or service, 计数但不lock/unlock server.
解决方案 »
- ListCtrl 如何更改鼠标移动到某一行的背景颜色。
- opencv训练检测篮球的问题
- 初学 cppunit ,搭建框架遇到问题
- 挂靠到另一个程序的对话框上的钩子程序
- 对于数据源不在本地的数据库操作是如何进行?
- ???如何visual c++6.0调用visual prolog
- 无法解析的外部符号 提供思路即可;我是用 win32 sdk 写的 (加入了 mfc 的支持)
- 谁有VC中用MD5对字符串进行加密的源码?
- 如何知道某窗口是否处于激活状态?
- 我想创建一个窗口,希望它被打开时是看不见的,应该使用窗口的什么风格???
- 结构化存储里如何重新打开一个流
- 关于用odbc加ado控件远程访问数据库的问题
#include <iostream>
using namespace std;class Base {
public:
virtual void fun() {
cout << "Base::fun" << endl;
} void doSomething() {
fun();
}
};class Drive : public Base {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};int main() {
Drive obj;
obj.doSomething(); return 0;
}=================== 模版 =====================#include <iostream>
using namespace std;template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
} void doSomething() {
T* pT = static_cast<T*>(this);
pT->fun();
}
};class Drive : public Base<Drive> {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};int main() {
Drive obj;
obj.doSomething(); return 0;
}
将子类以模版参数的形式传给基类,然后将基类"逆"转换为子类. 如果子类覆盖了原来基类的函数,就执行子类的函数---实现多态.
class CComObject : public Base
{
public:
... //if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) throw()
{return _InternalQueryInterface(iid, ppvObject);}
...
};template <class contained>
class CComAggObject :
public IUnknown,
public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >
{
public:
STDMETHOD(QueryInterface)(REFIID iid, 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;
}
}2. 而_InternalQueryInterface 是定义于宏BEGIN_COM_MAP中的,并且调用了CComObjectRootBase中的InternalQueryInterface。其中包含参数_GetEntries() ---这是接口映射表、#define BEGIN_COM_MAP(x) public: \
.....
HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) throw() \
{ return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); } \
const static ATL::_ATL_INTMAP_ENTRY* WINAPI _GetEntries() throw() { \
static const ATL::_ATL_INTMAP_ENTRY _entries[] = { DEBUG_QI_ENTRY(x)3. InternalQueryInterface由调用AtlInternalQueryInterface来遍历口映射表来查找接口,并获得接口指针 static HRESULT WINAPI InternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
// Only Assert here. AtlInternalQueryInterface will return the correct HRESULT if ppvObject == NULL
#ifndef _ATL_OLEDB_CONFORMANCE_TESTS
ATLASSERT(ppvObject != NULL);
#endif
ATLASSERT(pThis != NULL);
// First entry in the com map should be a simple map entry
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
#if defined(_ATL_DEBUG_INTERFACES) || defined(_ATL_DEBUG_QI)
LPCTSTR pszClassName = (LPCTSTR) pEntries[-1].dw;
#endif // _ATL_DEBUG_INTERFACES
HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvObject);
#ifdef _ATL_DEBUG_INTERFACES
_AtlDebugInterfacesModule.AddThunk((IUnknown**)ppvObject, pszClassName, iid);
#endif // _ATL_DEBUG_INTERFACES
return _ATLDUMPIID(iid, pszClassName, hRes);
}
4。而接口映射表是通过一组宏来实现的BEGIN_COM_MAP(CClass)
COM_INTERFACE_ENTRY(IA)
COM_INTERFACE_ENTRY(IB)
END_COM_MAP()// BEGIN_COM_MAP
#define BEGIN_COM_MAP(x) public: \
.....
HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) throw() \
{ return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); } \
const static ATL::_ATL_INTMAP_ENTRY* WINAPI _GetEntries() throw() { \
static const ATL::_ATL_INTMAP_ENTRY _entries[] = { DEBUG_QI_ENTRY(x)// COM_INTERFACE_ENTRY
#define COM_INTERFACE_ENTRY(x)\
{&_ATL_IIDOF(x), \
offsetofclass(x, _ComMapClass), \
_ATL_SIMPLEMAPENTRY},// END_COM_MAP
#ifdef _ATL_DEBUG
#define END_COM_MAP() \
__if_exists(_GetAttrEntries) {{NULL, (DWORD_PTR)_GetAttrEntries, _ChainAttr }, }\
{NULL, 0, 0}}; return &_entries[1];} \
virtual ULONG STDMETHODCALLTYPE AddRef( void) throw() = 0; \
virtual ULONG STDMETHODCALLTYPE Release( void) throw() = 0; \
STDMETHOD(QueryInterface)(REFIID, void**) throw() = 0;
#else
#define END_COM_MAP() \
__if_exists(_GetAttrEntries) {{NULL, (DWORD_PTR)_GetAttrEntries, _ChainAttr }, }\
{NULL, 0, 0}}; return _entries;} \
virtual ULONG STDMETHODCALLTYPE AddRef( void) throw() = 0; \
virtual ULONG STDMETHODCALLTYPE Release( void) throw() = 0; \
STDMETHOD(QueryInterface)(REFIID, void**) throw() = 0;
#endif // _ATL_DEBUG5. AtlInternalQueryInterface实现
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
ATLASSERT(pThis != NULL);
// First entry in the com map should be a simple map entry
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
if (ppvObject == NULL)
return E_POINTER;
*ppvObject = NULL;
if (InlineIsEqualUnknown(iid)) // use first interface
{
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
{
ATLASSERT(!bBlind);
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
else //actual function call
{
HRESULT hRes = pEntries->pFunc(pThis,
iid, ppvObject, pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}ATL实际是通过计算指针偏移量来获得指针的。
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
class ATL_NO_VTABLE CUse :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CUse, &CLSID_Parser>,
public IDispatchImpl<IUse1, &IID_IParser, &LIBID_netfeesrvLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
public IDispatchImpl<IUse1, &IID_IParser, &LIBID_netfeesrvLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public: ....
}2. 接口分布结构:
pI -----> vptr1 ------> IUnkonwn
| IDispatch
dw IUse1
|
----> vptr2 ------> IUnkonwn
IDispatch
IUse23. 接口映射表
BEGIN_COM_MAP(CUse)
COM_INTERFACE_ENTRY(IUse1)
COM_INTERFACE_ENTRY(IUse2)
COM_INTERFACE_ENTRY_IID(IDispatch, IUse2)
END_COM_MAP()#define COM_INTERFACE_ENTRY(x)\
{&_ATL_IIDOF(x), \
offsetofclass(x, _ComMapClass), \
_ATL_SIMPLEMAPENTRY},#define COM_INTERFACE_ENTRY_IID(iid, x)\
{&iid,\
offsetofclass(x, _ComMapClass),\
_ATL_SIMPLEMAPENTRY},IUse1 ---- offsetofclass(IUse1, _ComMapClass) ---_ATL_SIMPLEMAPENTRY
IUse2 ---- offsetofclass(IUse2, _ComMapClass) ---_ATL_SIMPLEMAPENTRY
IDispatch -- offsetofclass(IUse2, _ComMapClass) ---_ATL_SIMPLEMAPENTRY <======= 选择IUse2计算接口偏移4。
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
ATLASSERT(pThis != NULL);
// First entry in the com map should be a simple map entry
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
if (ppvObject == NULL)
return E_POINTER;
*ppvObject = NULL;
if (InlineIsEqualUnknown(iid)) // use first interface
{
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
{
ATLASSERT(!bBlind);
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
else //actual function call
{
HRESULT hRes = pEntries->pFunc(pThis,
iid, ppvObject, pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}通过
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
来通过偏移量获得接口指针 --- *ppvObject = pUnk;
即:基类指针(pUnk)象子类指针(pIUser1, pIUser2)逆转换。
pIUser1 = pUnk;
pIUser2 = pUnk;当要取得IDispatch时,根据程序作出的选择
IDispatch -- offsetofclass(IUse2, _ComMapClass) ---_ATL_SIMPLEMAPENTRY <======= 选择IUse2计算接口偏移
在这里就是计算出pIUse2的偏移量,取得接口指针 --- *ppvObject = pUnk;
即:基类指针(pUnk)象子类指针(pIDispatch)逆转换。
pIDispatch = pUnk;由此可以看出,当实现象IDispatch这样的多继承时,需要类设计者决定IDispatch的走向:
IDispatch -- offsetofclass(IUse2, _ComMapClass) ---_ATL_SIMPLEMAPENTRY <======= 选择IUse2计算接口偏移
在COM_INTERFACE_ENTRY_IID(IDispatch, IUse2)中:
IDispatch决定最终获得接口的类型;
而IUse2决定取得接口指针的偏移量
在COM_INTERFACE_ENTRY_IID(IDispatch, IUse2)中:
而IUse2决定取得接口指针的偏移量
IDispatch决定最终获得接口的类型--由*ppvObject = pUnk作隐式转换;
STDMETHOD(Rotate)(long nDegrees, long* pnOrientation) =0;
STDMETHOD(Twirl)(long nVelocity) =0;
};struct IRedSphere {
// Colored IUnknown methods
STDMETHOD(RedQueryInterface)( REFIID riid, void** ppv) =0;
STDMETHOD_(ULONG, RedAddRef)() =0;
STDMETHOD_(ULONG, RedRelease)() =0; // Uncolored ISphere methods
STDMETHOD(Rotate)(long nDegrees, long* pnOrientation) =0;
STDMETHOD(Twirl)(long nVelocity) =0;
};实现组件时使用了IRedSphere接口进行实现。
class CDesktopGlobe :
public CComObjectRootEx<CComSingleThreadModel>,
public IRedSphere,
public IGlobe,
public IPlanet {
public:
...
BEGIN_COM_MAP(CDesktopGlobe)
// Expose IRedShere when ISphere is requested
COM_INTERFACE_ENTRY_IID(IID_ISphere, IRedSphere) <=== 当查询ISphere接口时,返回IRedSphere接口。
COM_INTERFACE_ENTRY(IGlobe)
COM_INTERFACE_ENTRY(IPlanet)
END_COM_MAP()
...
// Colored method implementations
STDMETHODIMP RedQueryInterface(REFIID riid, void** ppv)
{ return GetUnknown()->QueryInterface(riid, ppv); }
STDMETHODIMP_(ULONG) RedAddRef() {
_ThreadModel::Increment(&m_cRefSphere);
return GetUnknown()->AddRef();
}
STDMETHODIMP_(ULONG) RedRelease() {
_ThreadModel::Decrement(&m_cRefSphere);
return GetUnknown()->Release();
}private:
long m_cRefSphere;
};
这样,客户端对于查询ISphere接口时返回IRedSphere接口毫不知晓,一样使用。
只是这时实际调用的是IRedSphere实现。void TryRotate(IUnknown* punk) {
ISphere* ps = 0; // Implicit AddRef really a call to RedAddRef
if(SUCCEEDED(punk->QueryInterface(IID_ISphere, (void**)&ps))) { <=== 实际返回的是IRedSphere接口
// ps actually points to an IRedSphere* ps->Rotate(); <=== IRedSphere实现
ps->Release(); // Really a call to RedRelease
}
}
COM_INTERFACE_ENTRY_IID(IDispatch, IUse2)
两者有异曲同工之妙!都是呼叫一个接口,返回另外一个接口.
只不过IUse2和IDispatch具有继承关系,而ISphere和IRedSphere接口布局一致.总之,能怎么作的根本是接口兼容.
COM_INTERFACE_ENTRY_IID(IDispatch, IUse2)
两者有异曲同工之妙!都是呼叫一个接口,返回另外一个接口.
只不过IUse2和IDispatch具有继承关系,而ISphere和IRedSphere接口布局一致.总之,能怎么作的根本是接口兼容,或可以互相转化.
|
|
pUnkOuter == NULL -----yes-----> "new" CComObject<CClass>
|
no
|
"new" CComAggObject<CClass>这里的"new",其实就是CComCreator.CreateInstance(pv, riid, ppv);
其中的pv = pUnkOuter ;template <class T1>
class CComCreator
{
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
ATLASSERT(ppv != NULL);
if (ppv == NULL)
return E_POINTER;
*ppv = NULL; HRESULT hRes = E_OUTOFMEMORY;
T1* p = NULL;
ATLTRY(p = new T1(pv))
if (p != NULL)
{
p->SetVoid(pv);
p->InternalFinalConstructAddRef();
hRes = p->FinalConstruct();
if (SUCCEEDED(hRes))
hRes = p->_AtlFinalConstruct();
p->InternalFinalConstructRelease();
if (hRes == S_OK)
hRes = p->QueryInterface(riid, ppv);
if (hRes != S_OK)
delete p;
}
return hRes;
}
};这个过程对于独立或聚合来说,没有什么大的区别。关于组件聚合,需要分两方面讨论:
1。聚合别的组件(外部)class ATL_NO_VTABLE CComplexMath :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CComplexMath, &CLSID_ComplexMath>,
public IDispatchImpl<IComplexMath, &IID_IComplexMath, &LIBID_COMPLEXCALCULATORLib>
{
public:
CComplexMath()
{
}DECLARE_REGISTRY_RESOURCEID(IDR_COMPLEXMATH)DECLARE_PROTECT_FINAL_CONSTRUCT()BEGIN_COM_MAP(CComplexMath)
COM_INTERFACE_ENTRY(IComplexMath)
COM_INTERFACE_ENTRY_AGGREGATE (IID_ISimpleMath, ptrUnk) <=== 增加要暴露的内部组件接口,到内部组件IUnknown接口的映射
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()// IComplexMath
public:
STDMETHOD(Divide)(/*[in]*/ int x, /*[in]*/ int y, /*[out,retval]*/ int* z );//Aggregation: Add the controlling macro, finalconstruct and final release
DECLARE_GET_CONTROLLING_UNKNOWN() <=== 获取外部IUnknown接口指针
IUnknown* ptrUnk; <=== 内部组件的IUnknown接口指针
HRESULT FinalConstruct()
{
return CoCreateInstance(CLSID_SimpleMath, GetControllingUnknown(),CLSCTX_ALL, IID_IUnknown, (void**)&ptrUnk); <=== 创建内部组件(引入pUnkOuter),并获得内部组件的IUnknown接口指针ptrUnk
}
void FinalRelease()
{
ptrUnk->Release(); <=== 释放内部组件的IUnknown接口指针
}
//End};聚合接口映射宏:
COM_INTERFACE_ENTRY_AGGREGATE (IID_ISimpleMath, ptrUnk)展开为:
#define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)\
{&iid,\
(DWORD_PTR)offsetof(_ComMapClass, punk),\ // dw中存储的是内部对象接口指针在外部对象中的偏移量
_Delegate},ATL使用_Delegate来支持聚合。
static HRESULT WINAPI _Delegate(void* pv, REFIID iid, void** ppvObject, DWORD_PTR dw)
{
HRESULT hRes = E_NOINTERFACE;
IUnknown* p = *(IUnknown**)((DWORD_PTR)pv + dw); // 通过偏移量得到内部接口指针
if (p != NULL)
hRes = p->QueryInterface(iid, ppvObject); // 查询得到所要求的内部接口指针
return hRes;
}ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
ATLASSERT(pThis != NULL);
// First entry in the com map should be a simple map entry
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
if (ppvObject == NULL)
return E_POINTER;
*ppvObject = NULL;
if (InlineIsEqualUnknown(iid)) // use first interface
{
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
{
ATLASSERT(!bBlind);
IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
else //actual function call
{
HRESULT hRes = pEntries->pFunc(pThis, <== 使用_Delegate来支持聚合。
iid, ppvObject, pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}由此可见,接口映射表就像一个“路由器”---将不同的接口请求转发到外部或内部组件。BEGIN_COM_MAP(CComplexMath)
COM_INTERFACE_ENTRY(IComplexMath)
COM_INTERFACE_ENTRY_AGGREGATE (IID_ISimpleMath, ptrUnk) <=== 增加要暴露的内部组件接口,到内部组件IUnknown接口的映射
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()2。被聚合的组件(内部)
一个组件以被聚合形式存在时,最关键在于---pUnkOuter 的引入(p = new T1(pv))。被聚合的组件实现了两套:AddRef, Release 和 QueryInterface。一套用于内部控制;
一套用于外部控制-- 通过pUnkOuter;
template <class contained>
class CComAggObject :
public IUnknown,
public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >
{
public:
.............. STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();} // 内部
STDMETHOD_(ULONG, Release)() // 内部
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
STDMETHOD(QueryInterface)(REFIID iid, 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(); ......... }
else
hRes = m_contained._InternalQueryInterface(iid, ppvObject); //
return hRes;
} ... CComContainedObject<contained> m_contained;
};template <class Base> //Base must be derived from CComObjectRoot
class CComContainedObject : public Base
{
public:
typedef Base _BaseClass;
CComContainedObject(void* pv) {m_pOuterUnknown = (IUnknown*)pv;}
#ifdef _ATL_DEBUG_INTERFACES
~CComContainedObject()
{
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
_AtlDebugInterfacesModule.DeleteNonAddRefThunk(m_pOuterUnknown);
}
#endif STDMETHOD_(ULONG, AddRef)() {return OuterAddRef();} // 外部
STDMETHOD_(ULONG, Release)() {return OuterRelease();} // 外部
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) // 外部
{
return OuterQueryInterface(iid, ppvObject);
}
template <class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
{
return QueryInterface(__uuidof(Q), (void**)pp);
}
//GetControllingUnknown may be virtual if the Base class has declared
//DECLARE_GET_CONTROLLING_UNKNOWN()
IUnknown* GetControllingUnknown()
{
#ifdef _ATL_DEBUG_INTERFACES
IUnknown* p;
_AtlDebugInterfacesModule.AddNonAddRefThunk(m_pOuterUnknown, _T("CComContainedObject"), &p);
return p;
#else
return m_pOuterUnknown;
#endif
}
};
COM_INTERFACE_ENTRY(IAdvance)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMath1, m_pSimpleUnknown)----------
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMath2, m_pSimpleUnknown)------- 路由
END_COM_MAP() |
|
|
(内部被聚合类)
BEGIN_COM_MAP(CSimple)
COM_INTERFACE_ENTRY(IID_IMath1)
COM_INTERFACE_ENTRY(IID_IMath2)
END_COM_MAP()
1.简单返回HRESULT
对于比较简单的错误,直接返回表示错误原因的 HRESULT。2.抛出COM异常---错误信息接口
既然 COM 是靠各种各样的接口来提供服务的,于是很自然地就会想到,是否有一个接口能够提供更丰富的错误信息报告那?答案是:ISupportErrorInfo。ATL 把上述的代码给我们包装成 CComCoClass::Error() 的6个重载函数了。函数调用过程如下:
Error --> AtlReportError --> AtlSetErrorInfo --> SetErrorInfo(0, pErrorInfo);template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass
{
public:
DECLARE_CLASSFACTORY()
DECLARE_AGGREGATABLE(T)
typedef T _CoClass;
static const CLSID& WINAPI GetObjectCLSID() {return *pclsid;}
static LPCTSTR WINAPI GetObjectDescription() {return NULL;}
static HRESULT WINAPI Error(LPCOLESTR lpszDesc, <=== Error
const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlReportError(GetObjectCLSID(), lpszDesc, iid, hRes);
}
static HRESULT WINAPI Error(LPCOLESTR lpszDesc, DWORD dwHelpID, <=== Error
LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlReportError(GetObjectCLSID(), lpszDesc, dwHelpID, lpszHelpFile,
iid, hRes);
}
static HRESULT WINAPI Error(UINT nID, const IID& iid = GUID_NULL, <=== Error
HRESULT hRes = 0, HINSTANCE hInst = _AtlBaseModule.GetResourceInstance())
{
return AtlReportError(GetObjectCLSID(), nID, iid, hRes, hInst);
}
static HRESULT WINAPI Error(UINT nID, DWORD dwHelpID,
LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL,
HRESULT hRes = 0, HINSTANCE hInst = _AtlBaseModule.GetResourceInstance()) <=== Error
{
return AtlReportError(GetObjectCLSID(), nID, dwHelpID, lpszHelpFile,
iid, hRes, hInst);
}
static HRESULT WINAPI Error(LPCSTR lpszDesc,
const IID& iid = GUID_NULL, HRESULT hRes = 0) <=== Error
{
return AtlReportError(GetObjectCLSID(), lpszDesc, iid, hRes);
}
static HRESULT WINAPI Error(LPCSTR lpszDesc, DWORD dwHelpID, <=== Error
LPCSTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlReportError(GetObjectCLSID(), lpszDesc, dwHelpID,
lpszHelpFile, iid, hRes);
} ...
};--> AtlReportError:
inline HRESULT WINAPI AtlReportError(const CLSID& clsid, LPCOLESTR lpszDesc,
const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlSetErrorInfo(clsid, lpszDesc, 0, NULL, iid, hRes, NULL);
}--> AtlSetErrorInfo:
ATLINLINE ATLAPI AtlSetErrorInfo(const CLSID& clsid, LPCOLESTR lpszDesc, DWORD dwHelpID,
LPCOLESTR lpszHelpFile, const IID& iid, HRESULT hRes, HINSTANCE hInst)
{
USES_CONVERSION;
TCHAR szDesc[1024];
szDesc[0] = NULL;
// For a valid HRESULT the id should be in the range [0x0200, 0xffff]
if (IS_INTRESOURCE(lpszDesc)) //id
{
UINT nID = LOWORD((DWORD_PTR)lpszDesc);
ATLASSERT((nID >= 0x0200 && nID <= 0xffff) || hRes != 0);
if (LoadString(hInst, nID, szDesc, 1024) == 0)
{
ATLASSERT(FALSE);
lstrcpy(szDesc, _T("Unknown Error"));
}
lpszDesc = T2OLE(szDesc);
if (hRes == 0)
hRes = MAKE_HRESULT(3, FACILITY_ITF, nID);
} CComPtr<ICreateErrorInfo> pICEI;
if (SUCCEEDED(CreateErrorInfo(&pICEI)))
{
CComPtr<IErrorInfo> pErrorInfo;
pICEI->SetGUID(iid);
LPOLESTR lpsz;
ProgIDFromCLSID(clsid, &lpsz);
if (lpsz != NULL)
pICEI->SetSource(lpsz);
if (dwHelpID != 0 && lpszHelpFile != NULL)
{
pICEI->SetHelpContext(dwHelpID);
pICEI->SetHelpFile(const_cast<LPOLESTR>(lpszHelpFile));
}
CoTaskMemFree(lpsz);
pICEI->SetDescription((LPOLESTR)lpszDesc);
if (SUCCEEDED(pICEI->QueryInterface(__uuidof(IErrorInfo), (void**)&pErrorInfo)))
SetErrorInfo(0, pErrorInfo); <====== 抛出COM异常
}
return (hRes == 0) ? DISP_E_EXCEPTION : hRes;
}
1.简单返回HRESULT
对于比较简单的错误,直接返回表示错误原因的 HRESULT。2.抛出COM异常---调用Error(...)
既然 COM 是靠各种各样的接口来提供服务的,于是很自然地就会想到,是否有一个接口能够提供更丰富的错误信息报告那?答案是:IErrorInfo(调用SetErrorInfo(0, pErrorInfo);)。ATL 把SetErrorInfo包装成 CComCoClass::Error() 的6个重载函数了。函数调用过程如下:
Error --> AtlReportError --> AtlSetErrorInfo --> SetErrorInfo(0, pErrorInfo);template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass
{
public:
DECLARE_CLASSFACTORY()
DECLARE_AGGREGATABLE(T)
typedef T _CoClass;
static const CLSID& WINAPI GetObjectCLSID() {return *pclsid;}
static LPCTSTR WINAPI GetObjectDescription() {return NULL;}
static HRESULT WINAPI Error(LPCOLESTR lpszDesc, <=== Error
const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlReportError(GetObjectCLSID(), lpszDesc, iid, hRes);
}
static HRESULT WINAPI Error(LPCOLESTR lpszDesc, DWORD dwHelpID, <=== Error
LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlReportError(GetObjectCLSID(), lpszDesc, dwHelpID, lpszHelpFile,
iid, hRes);
}
static HRESULT WINAPI Error(UINT nID, const IID& iid = GUID_NULL, <=== Error
HRESULT hRes = 0, HINSTANCE hInst = _AtlBaseModule.GetResourceInstance())
{
return AtlReportError(GetObjectCLSID(), nID, iid, hRes, hInst);
}
static HRESULT WINAPI Error(UINT nID, DWORD dwHelpID,
LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL,
HRESULT hRes = 0, HINSTANCE hInst = _AtlBaseModule.GetResourceInstance()) <=== Error
{
return AtlReportError(GetObjectCLSID(), nID, dwHelpID, lpszHelpFile,
iid, hRes, hInst);
}
static HRESULT WINAPI Error(LPCSTR lpszDesc,
const IID& iid = GUID_NULL, HRESULT hRes = 0) <=== Error
{
return AtlReportError(GetObjectCLSID(), lpszDesc, iid, hRes);
}
static HRESULT WINAPI Error(LPCSTR lpszDesc, DWORD dwHelpID, <=== Error
LPCSTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlReportError(GetObjectCLSID(), lpszDesc, dwHelpID,
lpszHelpFile, iid, hRes);
} ...
};--> AtlReportError:
inline HRESULT WINAPI AtlReportError(const CLSID& clsid, LPCOLESTR lpszDesc,
const IID& iid = GUID_NULL, HRESULT hRes = 0)
{
return AtlSetErrorInfo(clsid, lpszDesc, 0, NULL, iid, hRes, NULL);
}--> AtlSetErrorInfo:
ATLINLINE ATLAPI AtlSetErrorInfo(const CLSID& clsid, LPCOLESTR lpszDesc, DWORD dwHelpID,
LPCOLESTR lpszHelpFile, const IID& iid, HRESULT hRes, HINSTANCE hInst)
{
USES_CONVERSION;
TCHAR szDesc[1024];
szDesc[0] = NULL;
// For a valid HRESULT the id should be in the range [0x0200, 0xffff]
if (IS_INTRESOURCE(lpszDesc)) //id
{
UINT nID = LOWORD((DWORD_PTR)lpszDesc);
ATLASSERT((nID >= 0x0200 && nID <= 0xffff) || hRes != 0);
if (LoadString(hInst, nID, szDesc, 1024) == 0)
{
ATLASSERT(FALSE);
lstrcpy(szDesc, _T("Unknown Error"));
}
lpszDesc = T2OLE(szDesc);
if (hRes == 0)
hRes = MAKE_HRESULT(3, FACILITY_ITF, nID);
} CComPtr<ICreateErrorInfo> pICEI;
if (SUCCEEDED(CreateErrorInfo(&pICEI)))
{
CComPtr<IErrorInfo> pErrorInfo;
pICEI->SetGUID(iid);
LPOLESTR lpsz;
ProgIDFromCLSID(clsid, &lpsz);
if (lpsz != NULL)
pICEI->SetSource(lpsz);
if (dwHelpID != 0 && lpszHelpFile != NULL)
{
pICEI->SetHelpContext(dwHelpID);
pICEI->SetHelpFile(const_cast<LPOLESTR>(lpszHelpFile));
}
CoTaskMemFree(lpsz);
pICEI->SetDescription((LPOLESTR)lpszDesc);
if (SUCCEEDED(pICEI->QueryInterface(__uuidof(IErrorInfo), (void**)&pErrorInfo)))
SetErrorInfo(0, pErrorInfo); <====== 抛出COM异常
}
return (hRes == 0) ? DISP_E_EXCEPTION : hRes;
}
1.返回HRESULT2.截获COM异常--- GetErrorInfo()而截获COM异常,也有两个方式:
2.1 使用 API 方式调用组件HRESULT hr = spXXX->fun() // 调用组件功能
if( FAILED( hr ) ) // 如果发生了错误
{
CComQIPtr < ISupportErrorInfo > spSEI = spXXX; // 组件是否提供了 ISupportErrorInfo 接口?
if( spSEI ) // 如果支持,那么
{
hr = spSEI->InterfaceSupportsErrorInfo( IID_Ixxx ); // 是否支持 Ixxx 接口的错误处理?
if( SUCCEEDED( hr ) )
{ // 支持,太好了。取出错误信息
CComQIPtr < IErrorInfo > spErrInfo; // 声明 IErrorInfo 接口
hr = ::GetErrorInfo( 0, &spErrInfo ); <======== 截获COM异常
if( SUCCEEDED( hr ) )
{
CComBSTR bstrDes;
spErrInfo->GetDescription( &bstrDes ); // 取得错误描述
...... // 还可以取得其它的信息
2.2 使用 #import 等包装方式调用组件,然后抛出C++异常,再然后由客户端截获
1.使用 #import 等包装组件
比如:
为什么可以使用try/catch结构就可以截获COM异常呢?
因为,当你使用 #import 引入组件类型库后,编译器帮你包装为一个接口的C++类,而成员函数中,使用了throw,所以你就能catch了。具体它如何包装的,你编译后,打开 tlh 文件去看。如:
inline _variant_t ISet::GetValue ( _bstr_t Name ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = get_Value(Name, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this)); <==抛出C++异常
return _variant_t(_result, false);
}其中:
void __stdcall _com_issue_errorex(HRESULT, IUnknown*, REFIID) throw(_com_error);
_com_issue_errorex的实现MS没有开放源码,但应该就是使用COM异常信息来填充_com_error信息(比如m_hresult, IErrorInfo*等),然后抛出C++异常。*注意:_com_issue_errorex是如何巧妙实现COM异常到C++异常转换的。2. 客户端截获C++异常
try
{
...... // 调用组件功能
}
catch( _com_error &e )
{
e.Description(); // 取得错误描述信息
...... // 还可以调用 _com_error 函数取得其它信息
}_com_error的定义如下:
class _com_error {
public:
// Constructors
//
_com_error(HRESULT hr,
IErrorInfo* perrinfo = NULL,
bool fAddRef = false) throw();
_com_error(const _com_error& that) throw(); // Destructor
//
virtual ~_com_error() throw(); // Assignment operator
//
_com_error& operator=(const _com_error& that) throw(); // Accessors
//
HRESULT Error() const throw();
WORD WCode() const throw();
IErrorInfo * ErrorInfo() const throw(); // IErrorInfo method accessors
//
_bstr_t Description() const throw(_com_error);
DWORD HelpContext() const throw();
_bstr_t HelpFile() const throw(_com_error);
_bstr_t Source() const throw(_com_error);
GUID GUID() const throw(); // FormatMessage accessors
const TCHAR * ErrorMessage() const throw(); // EXCEPINFO.wCode <-> HRESULT mappers
static HRESULT WCodeToHRESULT(WORD wCode) throw();
static WORD HRESULTToWCode(HRESULT hr) throw();private:
enum {
WCODE_HRESULT_FIRST = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200),
WCODE_HRESULT_LAST = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF+1, 0) - 1
};
const HRESULT m_hresult;
IErrorInfo * m_perrinfo;
mutable TCHAR * m_pszMsg;
};*注意:其实_com_error就是对IErrorInfo的包装,用截获的COM异常来填充;
1。返回HRESULT
2。组件:SetErrorInfo(..., IErrorInfo *) 抛出COM异常
客户端:GetErrorInfo(..., IErrorInfo *) 截获COM异常而客户端之所以可以截获C++异常,主要是应为编译时对“返回HRESULT”和GetErrorInfo进行了包装。
就像这样,处理每个可能的错误,都要一大串代码 ---晕!HRESULT hr = spXXX->fun() // 调用组件功能
if( FAILED( hr ) ) // 如果发生了错误
{
CComQIPtr < ISupportErrorInfo > spSEI = spXXX; // 组件是否提供了 ISupportErrorInfo 接口?
if( spSEI ) // 如果支持,那么
{
hr = spSEI->InterfaceSupportsErrorInfo( IID_Ixxx ); // 是否支持 Ixxx 接口的错误处理?
if( SUCCEEDED( hr ) )
{ // 支持,太好了。取出错误信息
CComQIPtr < IErrorInfo > spErrInfo; // 声明 IErrorInfo 接口
hr = ::GetErrorInfo( 0, &spErrInfo ); <======== 截获COM异常
if( SUCCEEDED( hr ) )
{
CComBSTR bstrDes;
spErrInfo->GetDescription( &bstrDes ); // 取得错误描述
...... // 还可以取得其它的信息还好,微软聪明的工程师帮我们通过C++异常的方式,很巧妙地处理了这个问题。
{
spXXX->fun() // 调用组件功能
}
catch( _com_error &e )
{
e.Description(); // 取得错误描述信息
...... // 还可以调用 _com_error 函数取得其它信息
}
如果你认为足够了,OK,这就行了。但如果你要向错误信息中加入其它一些信息,这时就需要抛出COM异常---调用Error(...)。
然后由客户端截获异常,获得额外的信息。
1。返回HRESULT
2。组件:SetErrorInfo(..., IErrorInfo *) 抛出COM异常
客户端:GetErrorInfo(..., IErrorInfo *) 截获COM异常而组件中使用的Error(....)其实就是对SetErrorInfo的包装
客户端之所以可以截获C++异常,主要是因为编译时对“返回HRESULT”和GetErrorInfo进行了包装。
inline _variant_t ISet::GetValue ( _bstr_t Name ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = get_Value(Name, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this)); <==抛出C++异常
return _variant_t(_result, false);
}
经MS包装后,抛出异常的过程是这样的:
if (FAILED(_hr){
使用GetErrorInfo得到的信息,来填充_com_error
然后抛出 _com_error
}
所以,当使用 #import 等包装组件时,必须通过try{}catch{},来捕获错误。
所以,当使用 #import 等包装组件时,必须通过try{}catch{},来捕获错误。
class ATL_NO_VTABLE CSample :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CParser, &CLSID_Parser>,
public ISupportErrorInfo,
public IDispatchImpl<IParser, &IID_IParser, &LIBID_sampleLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:组件的ISupportErrorInfo接口其实就一个方法InterfaceSupportsErrorInfo,用来判断组件中某一接口是否支持截获异常。
STDMETHODIMP CParser::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IParser
}; for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}也就是说:只有接口位于arr表中时,客户端才可以截获异常。 static const IID* arr[] =
{
&IID_IParser
};总结:只有接口位于arr表中时,客户端才可以截获异常。但并不影响其在组件实现中抛出异常。为什么要这么作呢?为了效率?
而所谓的"正"转换---"由派生类转换成基类",是隐含转换,不需要type_cast.1."由派生类转换成基类"---好理解, 运用虚函数实现多态时,经常用.=================== 虚函数 =====================
#include <iostream>
using namespace std;class Base {
public:
virtual void fun() {
cout << "Base::fun" << endl;
}
};class Drive : public Base {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};int main() {
Base *pBase;
Drive *pDrive = new Drive;//
pBase = pDrive; <====== "由派生类转换成基类" ---正转换
pBase->fun(); <====== 调用Drive::funreturn 0;
}2.而"由基类转换成派生类"的"逆"转换什么时候用到呢?
-------那就是要实现模版形式的多态时!!=================== 模版 =====================#include <iostream>
using namespace std;template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
}void doSomething() {
T* pT = static_cast<T*>(this); <== "由基类转换成派生类"的"逆"转换
pT->fun();
}
};class Drive : public Base<Drive> {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};int main() {
Drive obj;
obj.doSomething();return 0;
}
2.而对多继承时,类型之间type_cast转换,指针的地址是可能变化的.
class Drive : public IBase1, public IBase2 {
}Drive *pDrive = new Drive;// "由派生类转换成基类"
IBase1 *pIBase1 = pDrive; // pIBase1 = pDrive
IBase1 *pIBase2 = pDrive; // pIBase2 != pDrive == 地址改变//"由基类转换成派生类"的"逆"转换
pDrive = type_cast<Drive *>pIBase1; //pDrive = pIBase1
pDrive = type_cast<Drive *>pIBase2; //pDrive != pIBase2
static_cast与reinterpret_cast
static_cast必须是对在具有继承关系的类型之间转换.
而对reinterpret_cast, 互相转换的类型之间可以没有继承关系.
就像它名字一样-----对转换后的内容"重新解释".
有点强制转换的意思!:)对reinterpret_cast, 在任何情况下reinterpret_cast转换,指针的地址不变.pIBase1 = reinterpret_cast<IBase1 *>pIBase2;pIBase1与pIBase2地址一样,但经reinterpret_cast"强制"转换后,pIBase1调用的IBase1的方法.
----很危险!那什么情况下会用到reinterpret_cast呢?
它检查被转换方的数据完整性.
因此dynamic_cast对从派生类向基类转换总是成功的.比如:
class CBase { };
class CDerived: public CBase { };CBase b; CBase* pb;
CDerived d; CDerived* pd;pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base
pd = dynamic_cast<CDerived*>(&b); // wrong: dynamic_cast不能将基类向派生类转换但dynamic_cast可以对虚类作"基类向派生类转换".但必须runtime检查"被转换方的数据完整性".例如:
class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; };int main () {
try {
CBase * pba = new CDerived;
CBase * pbb = new CBase;
CDerived * pd; pd = dynamic_cast<CDerived*>(pba);
if (pd==0) cout << "Null pointer on first type-cast" << endl; pd = dynamic_cast<CDerived*>(pbb);
if (pd==0) cout << "Null pointer on second type-cast" << endl; } catch (exception& e) {cout << "Exception: " << e.what();}
return 0;
}其中:CBase * pba = new CDerived;
CBase * pbb = new CBase;虽然pba 和pbb都指向CBase *类型.
但pba 实际指向完整的object of CDerived.
而pbb指向object of CBase, 非完整的object of CDerived.而dynamic_cast会runtime检查"被转换方的数据完整性".pd = dynamic_cast<CDerived*>(pba); <===== 成功! pba指向完整的object of CDerived.pd = dynamic_cast<CDerived*>(pbb); <===== 失败! pba指向非完整的object of CDerived.
总结:dynamic_cast就像它名字一样----要runtime检查"被转换方的数据完整性".
static_cast---靠程序员来检查类型转换的完整性
dynamic_cast---runtime检查类型转换的完整性
tear off只要用于当接口很多,容易造成接口"膨胀"的情况.
比如:
class CBeachBall :
public CComObjectRootEx<CBeachBall>,
public ISphere,
public IRollableObject,
public IPlaything,
public ILethalObject, <====不经常用
public ITakeUpSpace,
public IWishIWereMoreUseful,
public ITryToBeHelpful,
public IAmDepressed {...};CBeachBall一共实现了8个接口,这样每个组件实例都要由8个vptr,需要额外32个字节的开销.
而这8个接口并不是每个都很常用.比如ILethalObject就不经常,但还占用每个组件实例的4个字节的空间---浪费!!怎么办? 使用tear off技术来解决!不是ILethalObject不常用吗, 那就将其从接口列表中去掉!当真有ILethalObject接口请求时,动态生成
实现ILethalObject接口的实例,然后调用这个实例中的实现.如下:
CBeachBallLethalness实现了ILethalObject接口class CBeachBallLethalness :
public CComTearOffObjectBase<CBeachBall,
CComSingleThreadModel>,
public ILethalObject {
public:
BEGIN_COM_MAP(CBeachBallLethalness)
COM_INTERFACE_ENTRY(ILethalObject)
END_COM_MAP() // ILethalObject methods
STDMETHODIMP Kill() {
m_pOwner->m_gasFill = GAS_HYDROGEN;
m_pOwner->HoldNearOpenFlame();
return S_OK;
}
};去掉ILethalObject接口, CBeachBall变成:
class CBeachBall :
public CComObjectRootEx<CBeachBall>,
public ISphere,
public IRollableObject,
public IPlaything,
//public ILethalObject, // 去掉ILethalObject接口
public ITakeUpSpace,
public IWishIWereMoreUseful,
public ITryToBeHelpful,
public IAmDepressed {
public:
BEGIN_COM_MAP(CBeachBall)
COM_INTERFACE_ENTRY(ISphere)
COM_INTERFACE_ENTRY(IRollableObject)
COM_INTERFACE_ENTRY(IPlaything)
COM_INTERFACE_ENTRY_TEAR_OFF(IID_ILethalObject,
CBeachBallLethalness) // ILethalObject接口由CBeachBallLethalness实现
COM_INTERFACE_ENTRY(ITakeUpSpace)
COM_INTERFACE_ENTRY(IWishIWereMoreUseful)
COM_INTERFACE_ENTRY(ITryToBeHelpful)
COM_INTERFACE_ENTRY(IAmDepressed)
END_COM_MAP()
...
private:
GAS_TYPE m_gasFill;
void HoldNearOpenFlame();
// Tear-offs are generally friends
friend class CBeachBallLethalness; // CBeachBallLethalness作为友元加入
};
总结:tear off技术就是要去掉组件中--"不常用接口",减轻每个实例的内存开销.
只有当真正使用该接口时,在动态生成"实现该接口的组件",实现调用.
不管是COM异常处理,集合/枚举,还是连接点,处处都体现接口的光芒.如果你觉得某些实现必须要传递某个结构或一个数组---且慢!一定是接口设计没有到位!
接口粒度还不够小!1.传递结构数据的话,可以参考COM处理的实现
---用SetErrorInfo(,*IErrorInfo)和GetErrorInfo(,*IErrorInfo),来抛出或截获COM异常.
其实它也在作"传递有关异常的结构数据",但它采用的接口的方式.避免结构数据的传递.
最终结构数据的传递,是通过接口IErrorInfo中的一些get/set实现的.所以说,组件编程,要换脑子,避免以前养成的编程定势,处处要以接口方式思考问题.
可以将组件想象成一个芯片----只想怎样通过管脚来实现功能,而不是奢望打开芯片取数据---那是不可能的!!总结:组件编程,是不同于以往OP,OO编程的新的编程方式.-----一切都是接口!
深入解析ATL -- ¥69 -- 好像现在没卖的了