引用计数
COM接口采用引用计数来控制组件的生命周期。
主要用AddRef ,Release 来进行内存管理,当客户从组件中取得一个接口时,此引用计数值将增1。当客户使用完某个接口后,组件的引用计数值将减1。
当引用计数值为0时,组件即可将自己从内从中删除。
(1)正确使用引用计数的三条简单原则:
1)在返回之前调用AddRef.
2)使用完接口后调用Release.
3)在赋值之后调用AddRef(指针复制).
基本原则是避免在使用组件时,组件已被删除。
QueryInterface 和CreateInstance中已经调用了AddRef,因此不必再调用它。
但两者返回的IUnknow接口和一般接口,用完后都需要调用Release来释放。
一般而言,每当复制一个接口的指针时,都应该相应的增加引用计数。
IUnknown* pIUnknown=CreateInstance();
IX * pIX=NULL;
HRESULT hr= pIUnknown->QueryInterface(IID_IX,(void **)&pIX);
pIUnknown->Release();
if(SUCCEEDED(hr))
{
pIX->Fx(); //Fx()为功能函数
IX * pIX2=pIX; //复制指针,即增加了一个使用组件(的接口)的可能,
pIX2->AddRef(); //所以要增加计数,并在使用完之后release
pIX2->Fx();
pIX2->Release(); //
pIX->Release();
}
//上面代码中,作为复制指针pIX2,其生存周期与pIX相同,即,在pIX Release之前pIX2就已经不再使用了,
pIX保证了组件使用安全的前提下,pIX不用AddRef和Release是肯定没问题的。
但有时很难判断某些没有加上AddRef和Release的调用是不是正确,是优化还是程序错误。一般用智能来封装
引用计数,从而解决这个问题。(智能指针)
组件可以对其每一个接口分别维护一个引用计数,也可以对整个组件维护单个的引用计数。原则上选择了为每一个
接口单独维护一个引用计数而不是针对整个维护计数,原因,一,使程序调试更方便,二,支持系统资源的按需获取。
(2)AddRef\Release 实现
主要是改变成员变量m_cRef的数值。AddRef增加其数值,Release减小数值,并在此值为0时将组件删除。
简单实现:
ULONG _stdcall AddRef()
{
return ++m_cRef;//return InterlockIncrement(&m_cRef);
}
ULONG _stdcall Release()
{
if(--m_cRef==0)//if(InterlockDecrement(&m_cRef)==0)
{
delete this;
return 0;
}
return m_cRef;
}
一般用InterlockIncrement和InterlockDecrement来实现AddRef\Release; 可以确保同一时间只有同一线程来访问成员变量。
3)引用计数的优化原则
就像(1)中例子,pIX能保障在pIX2生命周期内,组件肯定会在内存内存留。即,那些生命周期嵌套在引用同一接口的指针的生命周期之内时,外层的已有引用计数,内层的就可以不要了。
注意那些生命周期重叠的指针。
(1)输出参数原则
如果接口指针以函数返回值或者以传出参数传出,那么在函数内部,返回参数前调用AddRef.
例 QueryInterface/CreateInstance;
(2)输入参数原则
若接口指针作为参数传递给一个函数,该函数不修改也将其返回调用者,此时无需调用AddRef\Release例:
void foo(pIX *pIX)
{
pIX->Fx();
}
因为函数的生命周期嵌套在调用者的生命周期之内的。
(3)输入输出参数原则
对于传递进来的接口指针,必须在给他附另外一个接口指针之前调用release;返回之前,还必须对输出参数指向的新接口调用AddRef;
4)局部变量原则
它们是在函数的生存周期内才存在,因此不需要调用AddRef\Release
5)全局变量原则
在传递给另一个函数之前,必须调用AddRef.
6)不确定的情况
此时需要调用AddRef\Release 以防万一