com组件将维护一个称作是引用计数的数值。当客户从组件取得一个接口时,此引用计数值将增1。当客户使用完某个接口后,组件的引用计数值将减1.当引用计数值为0时,组件即可将自己从内存中删除。
为什么要选择为每一个接口单独维护一个引用计数而不是针对整个组件维护引用计数呢?
主要有两个原因:一是使程序调试更为方便;另外一个原因是支持资源的按需获取。
1程序调试:
假设在程序中忘记对某个接口调用 Releae(其实很多人会犯这个错)。这样组件将永远不会被删除掉,因为只是在引用计数值0时delete才会被调用 。这时就需要找出接口在何时何处应该被释放掉。当然找起来是相当困难的。在只对整个组件维护一个接口的情况下,进行这种 查找更为因难了。此时必须检查使用了此组件所提供的所有接口的代码。但若组件支持对每个接口分别维护一个引用计数那么可以把查找的范围限制在某个特定的接口上。在某些情况下这可以节省大量时间。
2.资源的按需获取
在实现某个接口时可能需要大量的内存或其他资源。对于此种情况,可以在QueryInterface的实现中,在客户请求此接口时完成资源的分配。但若只对整个组件维护一个引用计数,组件将无法决定何时可以安全地将此些接口相关联的内存释放。但基对每个接口分别维护一个引用计数,那么决定何时可以将此内存释放将会容易得多。
正确使用引用计数三条简单的规则
1. 在返回之前调用 AddRef。对于那些建好些返回接口指针的函数,在返回之前应该相应的指针调用 AddRef。这些函数包括QueryInterface 及 CreateInstance。这样当客户从这种 函数得到一个接口后。它将无需调用 AddRef.
2.使用完接口之后调用 Release。在使用某个接口之后应该调用些接口的Release函数。
3.在赋值之后调用AddRef. 在将一个接口指针赋给另一个接口指针时,应调用 AddRef。换句话说,在建立接口的别外一个引用之后应增加相应组件的引用计数。
例1(针对第一二规则):
// 创建一个组件
IUnknown* pIUnknown = CreateInstance();
// 获取接口IX
IX* pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX);
// pIUnknown用完了,可以把计数减掉
pIUnknown->Release();
if (SUCCEEDED(hr))
{
pIX->Fx(); // 使用接口pIX
pIX->Release(); // 用完释放引用计数
}
例2(针对第三规则):
// 创建一个组件
IUnknown* pIUnknown = CreateInstance();
// 获取接口IX
IX* pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX);
// pIUnknown用完了,可以把计数减掉
pIUnknown->Release();
if (SUCCEEDED(hr))
{
pIX->Fx(); // 使用接口pIX
IX* pIX2 = pIX; // 复制接口pIX
pIX2->AddRef(); // 增加一个引用计数
pIX2->Fx(); // 使用接口pIX2
pIX2->Release(); // 用完释放引用计数
pIX->Release(); // 用完释放引用计数
}
引用计数接口
在客户看来,引用计数是处于接口级的而不是组件级的。担从实现的角度来看,谁的引用计数被记录下来实际上没有关系。客户可以一直接相信组件将记录每个接口本身维护引用计数值。但客户不能假设整个组件维护单个的引用计数。
对于客户而言,每一个接口被分别维护一个引用计数意味着客户应该对它将要使用的指针调用AddRef,而不是其他的什么指针。对于使用完了指针客户应该调用其Release。
IUnknow* pIUnknown = CreateInstance();
IX* pIX = NULL;
pIUnknown->QueryInterface(IID_IX, &pIX);
pIX->Fx();
IX* pIX2 = pIx;
pIUnknown->AddRef(); // 这里错了,应该是 pIX2->AddRef();
pIX2->Fx();
pIX2->Release;
pIUnknown->Release(); // 应该是pIX->Release();
pIUnknown->Release();
选择为每一个接口单独维护一个引用计数而不是针对整个组件维护引用计数的原因:
1. 使程序调试更为方便;
2. 支持资源的按需获取;
AddRef和Release的实现
可以通过增大和减少某个数的值而实现之。
另外要注意的是AddRef和Release的返回值没有什么意义,只是在程序调试中才可能会用得上.客户不应将此从此值当成是组件或其接口的精确引用数。
ULONG __stdcall CA::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall CA::Release()
{
if (InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef;
}
引用计数优化
前面的【正确使用引用计数三条简单的规则】说到“在赋值之后调用AddRef”。
// 创建一个组件
IUnknown* pIUnknown = CreateInstance();
// 获取接口IX
IX* pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX);
// pIUnknown用完了,可以把计数减掉
pIUnknown->Release();
if (SUCCEEDED(hr))
{
IX* pIX2 = pIX;
pIX2->AddRef();
pIX->Fx();
pIX2->Fx();
pIX2->Release();
pIX->Release();
}
上面的代码中只有当客户将pIX 释放时组件才会从内存中删除。而客户只是在用完了pIX和pIX2之后才会将pIX释放。正是由于组件只是在pIX被释放后才从内存中删除。因此可以保证在pIX2的生命期内组件一直存在。所以这个时候pIX2可以不用调用AddRef 和 Release(当然调了也是对的,只是不调可以提高效率)。因为pIX生命周期包含了pIX2的生命周期。
如果pIX生命周期不包含了pIX2的生命周期,就一定要对pIX2进行引用计数。如下:
IUnknown* pIUnknown = CreateInstance();
IX* pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX);
pIUnknown->Release();
if (SUCCEEDED(hr))
{
IX* pIX2 = pIX;
pIX2->AddRef();
pIX->Fx();
pIX->Release(); // pIX生命期结束
pIX2->Fx();
pIX2->Release();
pIX->Release();
}
为对引用计数进行优化,关键是找出那些生命期嵌套在引用同一人接口的指针生命期内的接口指针。但这不是一件容易的事情。但有些情况还是比较明显的。如果函数的情况。如下,函数foo的生命包含在pIX的生命期中。
Void fool(IX* pIX2)
{
pIX2->Fx();
}
IUnknown* pIUnknown = CreateInstance();
IX* pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX);
pIUnknown->Release();
if (SUCCEEDED(hr))
{
Foo(pIX);
pIX->Release();
}
下面给出一些引用计数的规则
引用计数规则
客户必须对每一个接口具有一个单独的引用计数值那样来处理各接口。因此,客户必须对不同的接口分别进行引用计数,即使它们的生命期是嵌套的。
一、输出参数规则
输出参数指的是给函数的调用者传回一个值的函数参数。从这一点上讲,输出参数的作用同函数的返回值是类似的。任何在输出参数中或作为返回值返回一个新的接口指针的函数必须对些接口指针调用AddRer
二、输入参数规则
对传入函数的接口指针,无需调用AddRef和Release,这是因为函数的生命期嵌套在调用者的生命期内。(见前面的例子)
三、输入-输出参数规则
输入-输出参数同时具有输入参数及输出参数的功能。在函数休中可以使用输入-输出参数的值,然后可以对这些值进行修改并将其返回给调用者。
在函数中,对于用输入-输出参数传递进来的接口指针,必须在给它赋另外一个接口指针值之前调用其Release。在函数返回之前,还必须对输出参数中所保存的接口指针调用AddRef.如下例所示。
Void ExchangeforCachedPtr(int i, IX **ppIX)
{
(*ppIX)->Fx(); // Do something with in-parameter.
(*ppIX)->Release(); // Release in parameter.
*ppIX = g_Cache[i]; // Get cached pointer
(*ppIX)->AddRef(); // AddRef pointer.
(*ppIX)->Fx(); // Do something with out-parameter.
}
四、局部变量规则
对于局部自制的接口指针,由于它们只是在函数的生命其内才存在,因此无需调用AddRef和Release。这条规则实际是输入参数规则的直接结果。在下面的例子中,pIX2只是在函数foo的生命期内都在,因此可以保证其生命期将嵌套在所传入的pIX指针的生命期,因此无需对pIX2调用AddRef和Release。
Void foo(IX *pIX)
{
IX *pIX2 = pIX;
pIX2->Fx();
}
五、全局变量规则
对于保存在全局变量中的接口指针,在将其传递给另外一个函数之前,必须调用其AddRef。由于此变量是全局性的,因此任何函数都可以通过调用其Release来终止其生命期。对于保存在成员变量中的接口指针,也应按此种方式进行处理。因为类中的任何成员函数都可以改变此种接口指针的状态。
六、不能确定时的规则
对于任何不定的情形,都应调用AddRef和Release对。
另外,在决定要进行优化时,应给那些没有进行引用计数的指针加上相应的注释,否则其它程序员在修改代码时,将可能会增大接口指针的生命期,从而合引用计数的优化遭到破坏。
忘记调用Release造成的错误可能比不调用AddRef造成的错误更难检测。
完整例子:
(vs2008)代码下载:http://www.box.net/shared/uvhhhqqbr2
#include <iostream>
using namespace std;
#include <objbase.h>
void trace(const char* msg)
{
cout << msg << endl;
}
// Forward references for GUIDs
extern const IID IID_IX;
extern const IID IID_IY;
extern const IID IID_IZ;
// Interfaces
interface IX : IUnknown
{
virtual void __stdcall Fx() = 0;
} ;
interface IY : IUnknown
{
virtual void __stdcall Fy() = 0;
} ;
interface IZ : IUnknown
{
virtual void __stdcall Fz() = 0;
} ;
//
// Component
//
class CA : public IX, public IY
{
// IUnknown implementation
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
// Interface IX implementation
virtual void __stdcall Fx() { cout << "Fx" << endl;}
// Interface IY implementation
virtual void __stdcall Fy() { cout << "Fy" << endl;}
public:
// Constructor
CA() : m_cRef(0) {}
// Destructor
~CA() { trace("CA: Destroy self.");}
private:
long m_cRef;
} ;
HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IUnknown)
{
trace("CA QI: Return pointer to IUnknown.");
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IX)
{
trace("CA QI: Return pointer to IX.");
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IY)
{
trace("CA QI: Return pointer to IY.");
*ppv = static_cast<IY*>(this);
}
else
{
trace("CA QI: Interface not supported.");
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
ULONG __stdcall CA::AddRef()
{
cout << "CA: AddRef = " << m_cRef+1 << '.' << endl;
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall CA::Release()
{
cout << "CA: Release = " << m_cRef-1 << '.' << endl;
if (InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef;
}
//
// Creation function
//
IUnknown* CreateInstance()
{
IUnknown* pI = static_cast<IX*>(new CA);
pI->AddRef();
return pI;
}
//
// IIDs
//
// {32bb8320-b41b-11cf-a6bb-0080c7b2d682}
static const IID IID_IX =
{0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
// {32bb8321-b41b-11cf-a6bb-0080c7b2d682}
static const IID IID_IY =
{0x32bb8321, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
// {32bb8322-b41b-11cf-a6bb-0080c7b2d682}
static const IID IID_IZ =
{0x32bb8322, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
//
// Client
//
int main()
{
HRESULT hr;
trace("Client: Get an IUnknown pointer.");
IUnknown* pIUnknown = CreateInstance();
trace("Client: Get interface IX.");
IX* pIX = NULL;
hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
trace("Client: Succeeded getting IX.");
pIX->Fx(); // Use interface IX.
pIX->Release();
}
trace("Client: Get interface IY.");
IY* pIY = NULL;
hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY);
if (SUCCEEDED(hr))
{
trace("Client: Succeeded getting IY.");
pIY->Fy(); // Use interface IY.
pIY->Release();
}
trace("Client: Ask for an unsupported interface.");
IZ* pIZ = NULL;
hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ);
if (SUCCEEDED(hr))
{
trace("Client: Succeeded in getting interface IZ.");
pIZ->Fz();
pIZ->Release();
}
else
{
trace("Client: Could not get interface IZ.");
}
trace("Client: Release IUnknown interface.") ;
pIUnknown->Release() ;
return 0;
}