引用計(jì)數(shù)
COM接口采用引用計(jì)數(shù)來(lái)控制組件的生命周期。
主要用AddRef ,Release 來(lái)進(jìn)行內(nèi)存管理,當(dāng)客戶從組件中取得一個(gè)接口時(shí),此引用計(jì)數(shù)值將增1。當(dāng)客戶使用完某個(gè)接口后,組件的引用計(jì)數(shù)值將減1。
當(dāng)引用計(jì)數(shù)值為0時(shí),組件即可將自己從內(nèi)從中刪除。
(1)正確使用引用計(jì)數(shù)的三條簡(jiǎn)單原則:
1)在返回之前調(diào)用AddRef.
2)使用完接口后調(diào)用Release.
3)在賦值之后調(diào)用AddRef(指針復(fù)制).
基本原則是避免在使用組件時(shí),組件已被刪除。
QueryInterface 和CreateInstance中已經(jīng)調(diào)用了AddRef,因此不必再調(diào)用它。
但兩者返回的IUnknow接口和一般接口,用完后都需要調(diào)用Release來(lái)釋放。
一般而言,每當(dāng)復(fù)制一個(gè)接口的指針時(shí),都應(yīng)該相應(yīng)的增加引用計(jì)數(shù)。
IUnknown* pIUnknown=CreateInstance();
IX * pIX=NULL;
HRESULT hr= pIUnknown->QueryInterface(IID_IX,(void **)&pIX);
pIUnknown->Release();
if(SUCCEEDED(hr))
{
pIX->Fx(); //Fx()為功能函數(shù)
IX * pIX2=pIX; //復(fù)制指針,即增加了一個(gè)使用組件(的接口)的可能,
pIX2->AddRef(); //所以要增加計(jì)數(shù),并在使用完之后release
pIX2->Fx();
pIX2->Release(); //
pIX->Release();
}
//上面代碼中,作為復(fù)制指針pIX2,其生存周期與pIX相同,即,在pIX Release之前pIX2就已經(jīng)不再使用了,
pIX保證了組件使用安全的前提下,pIX不用AddRef和Release是肯定沒(méi)問(wèn)題的。
但有時(shí)很難判斷某些沒(méi)有加上AddRef和Release的調(diào)用是不是正確,是優(yōu)化還是程序錯(cuò)誤。一般用智能來(lái)封裝
引用計(jì)數(shù),從而解決這個(gè)問(wèn)題。(智能指針)
組件可以對(duì)其每一個(gè)接口分別維護(hù)一個(gè)引用計(jì)數(shù),也可以對(duì)整個(gè)組件維護(hù)單個(gè)的引用計(jì)數(shù)。原則上選擇了為每一個(gè)
接口單獨(dú)維護(hù)一個(gè)引用計(jì)數(shù)而不是針對(duì)整個(gè)維護(hù)計(jì)數(shù),原因,一,使程序調(diào)試更方便,二,支持系統(tǒng)資源的按需獲取。
(2)AddRef\Release 實(shí)現(xiàn)
主要是改變成員變量m_cRef的數(shù)值。AddRef增加其數(shù)值,Release減小數(shù)值,并在此值為0時(shí)將組件刪除。
簡(jiǎn)單實(shí)現(xiàn):
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來(lái)實(shí)現(xiàn)AddRef\Release; 可以確保同一時(shí)間只有同一線程來(lái)訪問(wèn)成員變量。
3)引用計(jì)數(shù)的優(yōu)化原則
就像(1)中例子,pIX能保障在pIX2生命周期內(nèi),組件肯定會(huì)在內(nèi)存內(nèi)存留。即,那些生命周期嵌套在引用同一接口的指針的生命周期之內(nèi)時(shí),外層的已有引用計(jì)數(shù),內(nèi)層的就可以不要了。
注意那些生命周期重疊的指針。
(1)輸出參數(shù)原則
如果接口指針以函數(shù)返回值或者以傳出參數(shù)傳出,那么在函數(shù)內(nèi)部,返回參數(shù)前調(diào)用AddRef.
例 QueryInterface/CreateInstance;
(2)輸入?yún)?shù)原則
若接口指針作為參數(shù)傳遞給一個(gè)函數(shù),該函數(shù)不修改也將其返回調(diào)用者,此時(shí)無(wú)需調(diào)用AddRef\Release例:
void foo(pIX *pIX)
{
pIX->Fx();
}
因?yàn)楹瘮?shù)的生命周期嵌套在調(diào)用者的生命周期之內(nèi)的。
(3)輸入輸出參數(shù)原則
對(duì)于傳遞進(jìn)來(lái)的接口指針,必須在給他附另外一個(gè)接口指針之前調(diào)用release;返回之前,還必須對(duì)輸出參數(shù)指向的新接口調(diào)用AddRef;
4)局部變量原則
它們是在函數(shù)的生存周期內(nèi)才存在,因此不需要調(diào)用AddRef\Release
5)全局變量原則
在傳遞給另一個(gè)函數(shù)之前,必須調(diào)用AddRef.
6)不確定的情況
此時(shí)需要調(diào)用AddRef\Release 以防萬(wàn)一