zl程序教程

您现在的位置是:首页 >  后端

当前栏目

COM编程之四 引用计数

编程 引用 com 计数 之四
2023-09-14 09:12:20 时间

【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.

顺序  选择  循环  总结