【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.
顺序 选择 循环 总结