引用計數
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 以防萬一