第四章 引用计数
这章引出了一个控制权的问题,谁将控制组件的生存期,落到语言层面就是,谁将负责new和delete.在所有组件都要继承的IUnknown接口中AddRef和Release这两个成员负责了组件生命期的管理.从而可以去掉客户代码中的delete语句.
对客户而言,它并不知道COM组件的细节,以前觉得COM编程会给用户提供一个头文件,但现在看来不是如此,客户必须使用QueryInterface来查询COM有哪些接口,这有有一个问题,客户应该给COM组件一个什么样的搜索条件呢,也就是客户是如何知道每个IID对应的接口的功能和调用方法呢?我想随着时间的推移,这个问题应该会逐步明晰,现在还是先来看引用计数.
AddRef和Release实现的是一种名为引用计数的内存管理技术.引用计数的内存管理技术.引用计数是使组件能够自己将自己删除的最简单同时也是效率最高的方法.COM组件将维护一个称作是引用计数的数值.当客户从组件取得一个接口时,此引用计数值将增1.当客户使用完某个接口后,组件的引用计数值将减1.当引用计数值为0时,组件即可将自己从内存中删除.
对每个接口维护一个引用计数将更方便调试.更有利于实现资源的分配,因为可能出现这样一种情况,在实现某个接口时可能需要大量的内存或其他资源,可以在QueryInterface的实现中,在客户请求此接口时完成资源的分配,但若只对整个组件维护一个引用计数,组件将无法决定何时可以安全地将同此接口相关联的内存释放.但下面的例子是为了简化起见,对整个组件维护单个的引用计数值,下面是实例代码:
接口类 interface.h
// Interfaces
interface IX:IUnknown
... {
virtual void _stdcall Fx() = 0;
} ;
interface IY:IUnknown
... {
virtual void _stdcall Fy() = 0;
} ;
interface IZ:IUnknown
... {
virtual void _stdcall Fz() = 0;
} ;
// IIDs
static const IID IID_IX =
... {
0x32bb8320,0xb41b,0x11cf,...{0xa6,0xbb,0x0,0x80,0xc7,0xb2,0xd6,0x82}
} ;
static const IID IID_IY =
... {
0x32bb8321,0xb41b,0x11cf,...{0xa6,0xbb,0x0,0x80,0xc7,0xb2,0xd6,0x82}
} ;
static const IID IID_IZ =
... {
0x32bb8322,0xb41b,0x11cf,...{0xa6,0xbb,0x0,0x80,0xc7,0xb2,0xd6,0x82}
} ;
实现类 --头文件CB.h
#define CB_H
#include " interface.h "
#include < iostream.h >
extern void trace( const char * msg);
// Creation function
extern IUnknown * CreateInstance();
// Component
class CB: public IX, public IY
... {
virtual HRESULT _stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG _stdcall AddRef();
virtual ULONG _stdcall Release();
//interfac IX implementation
virtual void _stdcall Fx()...{cout<<"Fx()"<<endl;}
//interfac IY implementation
virtual void _stdcall Fy()...{cout<<"Fy()"<<endl;}
public:
CB():m_cRef(0)...{};
~CB()...{trace("CB: Destroy Self.");}
private:
long m_cRef;
} ;
#endif
实现类--CB.cpp
HRESULT _stdcall CB::QueryInterface( const IID & iid, void ** ppv)
... {
if (iid == IID_IUnknown)
...{
trace("QuerInterface:Return pointer to IUnknown");
*ppv = static_cast<IX*>(this);
}
else if(iid == IID_IX)
...{
trace("QueryInterface: Return pointer to IX.");
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IY)
...{
trace("QueryInterface: Return pointer to IY.");
*ppv = static_cast<IY*>(this);
}
else
...{
trace("QueryInterface: Interface not supported.");
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
ULONG _stdcall CB::AddRef()
... {
cout<<"CB: AddRef = "<<m_cRef+1<<'.'<<endl;
return InterlockedIncrement(&m_cRef);
}
ULONG _stdcall CB::Release()
... {
cout<<"CB: Release = "<<m_cRef-1<<'.'<<endl;
if (InterlockedDecrement(&m_cRef) == 0)
...{
delete this;
return 0;
}
return m_cRef;
}
void trace( const char * msg)
... {
cout<<msg<<endl;
}
// Creation function
IUnknown * CreateInstance()
... {
IUnknown* pI = static_cast<IX*>(new CB);
pI->AddRef();
return pI;
}
客户端代码--App.cpp
int main()
... {
HRESULT hr;
trace("Client: Get an IUnknown pointer.");
IUnknown* pIUnknown = CreateInstance();
trace("Client: Get Interface IX.");
IX *pIX = NULL;
hr = pIUnknown->QueryInterface(IID_IX,(void**)&pIX);
if (SUCCEEDED(hr))
...{
trace("Client: Succeeded getting IX.");
pIX->Fx();
pIX->Release();
}
trace("Client: Get Interface IY.");
IY *pIY = NULL;
hr = pIUnknown->QueryInterface(IID_IY,(void**)&pIY);
if (SUCCEEDED(hr))
...{
trace("Client: Succeeded getting IY.");
pIY->Fy();
pIY->Release();
}
trace("Client: Ask for an unsuppoered interface.");
IZ *pIZ = NULL;
hr = pIUnknown->QueryInterface(IID_IZ,(void**)&pIZ);
if (SUCCEEDED(hr))
...{
trace("Client: Succeeded getting IZ.");
pIZ->Fz();
pIZ->Release();
}
else
...{
trace("Client: Could not get interface IZ.");
}
trace("Client: Release IUnknown interface.");
pIUnknown->Release();
return 0;
}
/**/ /*输出结果
Client: Get an IUnknown pointer.
CB: AddRef = 1.
Client: Get Interface IX.
QueryInterface: Return pointer to IX.
CB: AddRef = 2.
Client: Succeeded getting IX.
Fx()
CB: Release = 1.
Client: Get Interface IY.
QueryInterface: Return pointer to IY.
CB: AddRef = 2.
Client: Succeeded getting IY.
Fy()
CB: Release = 1.
Client: Ask for an unsuppoered interface.
QueryInterface: Interface not supported.
Client: Could not get interface IZ.
Client: Release IUnknown interface.
CB: Release = 0.
CB: Destroy Self.
Press any key to continue
*/
在上面的代码我们可以发现,把子类的对象赋值给父类的指针是有道理的,从面向对象的思想来看,继承一方面是为了共用,另一方面是为了保证接口稳定性.从COM编程的思想来看,继承主要是为了实现多态,保证接口稳定性,可以从COM接口不包含数据成员可以看出(继承共用的也就是父类的数据成员).所以只要子类实现了父类的接口,我们就可以用父类的指针来操作子类的对象,也就是苹果一定是水果,所以我用水果来操作苹果肯定没错.下面的代码是本例中截取出来的,可见一斑: