COM编程之四 引用计数
【1】客戶為什么不應直接控制組件的生命期?
假設一個組件A正在使用另一個組件B,可想組件A(客戶)代碼中肯定有若干個指向組件B接口的指針。
那么這種情況下,當使用完一個接口而仍然在使用另一個接口時,是不能將組件釋放掉的。
而且很難知道兩個接口指針是否指向同一組件,因此決定何時可以安全的釋放一個組件將是極為困難的。
得知兩個接口指針是否是指向同一對象的唯一方法是查詢這兩個接口的IUnknown接口指針,然后對兩者結果進行比較。
當程序越來越復雜時,決定何時可以釋放一個組件是極為復雜的事情。
解決這個技術問題的辦法:我們可以通知組件何時需要使用它的某個接口以及何時使用完接口,而不是直接將接口刪除。
對組件的釋放也應該由組件在客戶使用完其各個接口之后自己完成。
IUnknown的兩個成員函數AddRef和Release的作用就是給客戶提供一種讓它指示何時處理完一個接口的手段。
【2】引用計數簡介
AddRef和Release實現的是一種名為引用計數的內存管理計數。
引用計數是使組件能夠自己將自己刪除的最簡單同時也是效率最高的方法。
COM組件將維護一個稱作是引用計數的數值。
當客戶從組件取得一個接口時,此引用計數將增1。
當客戶使用完某個接口后,組件的引用計數將減1。
當引用計數值為0時,組件即可將自己從內存中刪除。
當創建某個已有接口的另外一個引用時,客戶也將會增大相應組件的引用計數值。
【3】正確地使用引用計數,遵循的規則
(1)在返回之前調用AddRef。
對于那些返回接口指針的函數,在返回之前應用相應的指針調用AddRef。
這些函數包括QueryInterface及CreateInstance。
這樣當客戶從這種函數得到一個接口后,它將無需調用AddRef。
(2)使用完接口調用Release。
在使用完某個接口之后應調用此接口的Release函數。
以上兩條代碼如下:
1 //Create a new component 2 IUnknown* pLUnknown = CreateInstance(); 3 //Get interface IX 4 IX* pIX = NULL; 5 HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX); 6 if (SUCCEEDED(hr)) 7 { 8 pIX->fx(); //Use Interface IX 9 pIX->Release(); 10 } 11 pLUnknown->Release();(3)在賦值之后調用AddRef。
在將一個接口指針賦給另外一個接口指針時,應調用AddRef。
換句話說,在建立接口的另外一個應用之后應增加相應組件的應用計數。
代碼如下:
1 //Create a new component 2 IUnknown* pLUnknown = CreateInstance(); 3 //Get interface IX 4 IX* pIX = NULL; 5 HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX); 6 if (SUCCEEDED(hr)) 7 { 8 pIX->fx(); //Use Interface IX 9 IX* pIX2 = pIX; //Make a copy of pIX 10 11 pIX2->AddRef(); 12 pIX2->fx(); //Do something 13 pIX2->Release(); 14 15 pIX->Release(); 16 }【4】引用計數的完整示例
代碼如下:
1 // 2 // RefCount.cpp 3 // To compile, use: cl RefCount.cpp UUID.lib 4 // 5 #include <iostream> 6 using namespace std; 7 #include <objbase.h> 8 9 void trace(const char* msg) { cout << msg << endl ;} 10 11 // Forward references for GUIDs 12 extern const IID IID_IX ; 13 extern const IID IID_IY ; 14 extern const IID IID_IZ ; 15 16 // Interfaces 17 interface IX : IUnknown 18 { 19 virtual void __stdcall Fx() = 0 ; 20 } ; 21 22 interface IY : IUnknown 23 { 24 virtual void __stdcall Fy() = 0 ; 25 } ; 26 27 interface IZ : IUnknown 28 { 29 virtual void __stdcall Fz() = 0 ; 30 } ; 31 32 33 // 34 // Component 35 // 36 class CA : public IX, public IY 37 { 38 // IUnknown implementation 39 virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ; 40 virtual ULONG __stdcall AddRef() ; 41 virtual ULONG __stdcall Release() ; 42 43 // Interface IX implementation 44 virtual void __stdcall Fx() { cout << "Fx" << endl ;} 45 46 // Interface IY implementation 47 virtual void __stdcall Fy() { cout << "Fy" << endl ;} 48 49 public: 50 // Constructor 51 CA() : m_cRef(0) {} 52 53 // Destructor 54 ~CA() { trace("CA: Destroy self.") ;} 55 56 private: 57 long m_cRef; 58 } ; 59 60 HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) 61 { 62 if (iid == IID_IUnknown) 63 { 64 trace("CA QI: Return pointer to IUnknown.") ; 65 *ppv = static_cast<IX*>(this) ; 66 } 67 else if (iid == IID_IX) 68 { 69 trace("CA QI: Return pointer to IX.") ; 70 *ppv = static_cast<IX*>(this) ; 71 } 72 else if (iid == IID_IY) 73 { 74 trace("CA QI: Return pointer to IY.") ; 75 *ppv = static_cast<IY*>(this) ; 76 } 77 else 78 { 79 trace("CA QI: Interface not supported.") ; 80 *ppv = NULL ; 81 return E_NOINTERFACE; 82 } 83 reinterpret_cast<IUnknown*>(*ppv)->AddRef() ; 84 return S_OK ; 85 } 86 87 ULONG __stdcall CA::AddRef() 88 { 89 cout << "CA: AddRef = " << m_cRef+1 << '.' << endl ; 90 return InterlockedIncrement(&m_cRef) ; 91 } 92 93 ULONG __stdcall CA::Release() 94 { 95 cout << "CA: Release = " << m_cRef-1 << '.' << endl ; 96 97 if (InterlockedDecrement(&m_cRef) == 0) 98 { 99 delete this ; 100 return 0 ; 101 } 102 return m_cRef ; 103 } 104 105 // 106 // Creation function 107 // 108 IUnknown* CreateInstance() 109 { 110 IUnknown* pI = static_cast<IX*>(new CA) ; 111 pI->AddRef() ; 112 return pI ; 113 } 114 115 // 116 // IIDs 117 // 118 // {32bb8320-b41b-11cf-a6bb-0080c7b2d682} 119 static const IID IID_IX = 120 {0x32bb8320, 0xb41b, 0x11cf, 121 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; 122 123 // {32bb8321-b41b-11cf-a6bb-0080c7b2d682} 124 static const IID IID_IY = 125 {0x32bb8321, 0xb41b, 0x11cf, 126 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; 127 128 // {32bb8322-b41b-11cf-a6bb-0080c7b2d682} 129 static const IID IID_IZ = 130 {0x32bb8322, 0xb41b, 0x11cf, 131 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; 132 133 // 134 // Client 135 // 136 int main() 137 { 138 HRESULT hr ; 139 140 trace("Client: Get an IUnknown pointer.") ; 141 IUnknown* pIUnknown = CreateInstance() ; 142 143 144 trace("Client: Get interface IX.") ; 145 146 IX* pIX = NULL ; 147 hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX) ; 148 149 if (SUCCEEDED(hr)) 150 { 151 trace("Client: Succeeded getting IX.") ; 152 pIX->Fx() ; // Use interface IX. 153 pIX->Release() ; 154 } 155 156 157 trace("Client: Get interface IY.") ; 158 159 IY* pIY = NULL ; 160 hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY) ; 161 if (SUCCEEDED(hr)) 162 { 163 trace("Client: Succeeded getting IY.") ; 164 pIY->Fy() ; // Use interface IY. 165 pIY->Release() ; 166 } 167 168 169 trace("Client: Ask for an unsupported interface.") ; 170 171 IZ* pIZ = NULL ; 172 hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ) ; 173 if (SUCCEEDED(hr)) 174 { 175 trace("Client: Succeeded in getting interface IZ.") ; 176 pIZ->Fz() ; 177 pIZ->Release() ; 178 } 179 else 180 { 181 trace("Client: Could not get interface IZ.") ; 182 } 183 184 185 trace("Client: Release IUnknown interface.") ; 186 pIUnknown->Release() ; 187 188 return 0; 189 } 190 //Output 191 /* 192 Client: Get an IUnknown pointer. 193 CA: AddRef = 1. 194 Client: Get interface IX. 195 CA QI: Return pointer to IX. 196 CA: AddRef = 2. 197 Client: Succeeded getting IX. 198 Fx 199 CA: Release = 1. 200 Client: Get interface IY. 201 CA QI: Return pointer to IY. 202 CA: AddRef = 2. 203 Client: Succeeded getting IY. 204 Fy 205 CA: Release = 1. 206 Client: Ask for an unsupported interface. 207 CA QI: Interface not supported. 208 Client: Could not get interface IZ. 209 Client: Release IUnknown interface. 210 CA: Release = 0. 211 CA: Destroy self. 212 */【5】引用計數的優化
正確使用引用計數規則(3)的示例代碼做優化如下:
1 //Create a new component 2 IUnknown* pLUnknown = CreateInstance(); 3 //Get interface IX 4 IX* pIX = NULL; 5 HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX); 6 if (SUCCEEDED(hr)) 7 { 8 pIX->fx(); //Use Interface IX 9 IX* pIX2 = pIX; //Make a copy of pIX 10 11 // pIX2->AddRef(); //unnecessary!!! 12 pIX2->fx(); //Do something 13 // pIX2->Release(); //unnecessary!!! 14 15 pIX->Release(); 16 }關鍵是找出那些生命期嵌套在引用同一接口指針生命期內的接口指針。
1 void Fun(IX* pIx) 2 { 3 pIx->Fx(); 4 } 5 6 void main() 7 { 8 IUnknown* pLUnknown = CreateInstance(); 9 //Get interface IX 10 IX* pIX = NULL; 11 HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX); 12 if (SUCCEEDED(hr)) 13 { 14 Fun(pIX); 15 pIX->Release(); 16 } 17 }很顯然,Fun的生命期包含在pIX的生命期中,因此對于傳遞給Fun的接口指針,無需調用AddRef和Release。
在函數中,不必要對存在于局部變量的接口指針進行引用計數。
因為局部變量的生命期同函數的生命期是一樣的,因此也將包含在調用者的生命期內。
但當從某個全部變量或向某個全局變量復制一個指針時,則需要對此進行引用計數。
因為全局變量可以從任意函數中的任意地方被釋放。
【6】引用計數規則
(1)輸出參數規則
輸出參數指的是給函數的調用者傳回一個值的函數參數。比如QueryInterface函數
(2)輸入參數規則。比如上面引用計數優化的例子。
(3)輸入輸出參數規則
對于輸入輸出參數傳遞進來的接口指針,必須在給它賦另外一個接口指針之前調用其Release。
在函數返回之前,還必須對輸出參數中所保存的接口指針調用AddRef。示例代碼如下:
1 void ExchangeForCachedPtr(int i, IX**ppIX) 2 { 3 (*ppIX)->Fx(); 4 (*ppIX)->Release(); 5 *ppIX = g_cache[i]; 6 (*ppIX)->AddRef(); 7 (*ppIX)->Fx(); 8 }(4)局部變量規則
(5)全局變量規則
(6)不能確定時的規則
對于任何不能確定的情形,都應調用AddRef 和 Release對。
?
總而言之,通過引用計數,客戶可以控制接口的生命期,而組件本身可以決定何時將它從內存中刪除。
?
Good ?Good Study, Day Day Up.
順序 ?選擇 ?循環 ?總結
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的COM编程之四 引用计数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: COM编程之二 接口
- 下一篇: COM编程之五 动静态链接