玩技术,Geeker
一个原创技术文章分享网站

COM编程——引用计数(2)

前言

在上一篇《COM编程——引用计数(1)》中对AddRef和Release的实现机制进行了详细的总结,而这篇博文主要是对AddRef和Release的使用进行总结;主要从引用计数优化和引用计数使用规则两个方面进行总结。

引用计数优化

在上一篇《COM编程——引用计数(1)》中也总结了使用AddRef和Release的三条规则,在分析第三条规则时,也说了,有的时候AddRef和Release不是必须需要的;也就是说,在有些情况下,是不需要调用AddRef和Release的;因此,我们可以对调用的AddRef和Release进行必要的优化。再次将上一篇博文的那段代码贴出来:

IUnknown *pIUnknown = CreateInstance();
IX *pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void **)&pIX);
pIUnknown->Release(); // If we will not use the interface, we should release the interface right now
if (SUCCEEDED(hr))
{
     pIX->Fx();
     IX *pIX2 = pIX;
     pIX2->AddRef();
     pIX2->Fx();
     pIX2->Release();
     pIX->Release();
}

当我将pIX赋给pIX2之后,对pIX2调用了AddRef,最后,也对pIX2也对应的调用了Release。但是,我们也注意到pIX调用了Release是在pIX2之后的;也就是说,如果pIX没有调用Release,那么,组件就一直不会被销毁掉;由于对pIX2的操作都是在pIX->Release()之前,正是因为这个原因,确保了组件不会被销毁掉,所以对于pIX2的AddRef和Release就不是必须的了。

为了确保组件一直都存在于内存中,使用pIX的单个引用计数就够了。之所以会这样,关键之处在于pIX2的生命周期包含在pIX的生命周期内,也就是说pIX2嵌套在pIX内部了。再来看以下代码:

IUnknown *pIUnknown = CreateInstance();
IX *pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void **)&pIX);
pIUnknown->Release(); // If we will not use the interface, we should release the interface right now
if (SUCCEEDED(hr))
{
     pIX->Fx();
     IX *pIX2 = pIX;
     pIX2->AddRef();

     pIX->Fx();
     pIX->Release();

     pIX2->Fx();
     pIX2->Release();
}

可以看到,pIX2的生命周期不在pIX的生命周期内;所以,就需要单独的对pIX2进行引用计数。

上面的例子都很简单,我们一眼就看出了哪些AddRef和Release是多余的。但是,在实际项目中,找出那些嵌套的生命周期是一件非常困难的事情;比如有以下代码:

void Foo(IX *pIX2)
{
     pIX2->Fx();
}

IUnknown *pIUnknown = CreateInstance();
IX *pIX = NULL;
HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void **)&pIX);
pIUnknown->Release(); // If we will not use the interface, we should release the interface right now
if (SUCCEEDED(hr))
{
    Foo(pIX);
    pIX->Release();
}

我使用了Foo函数,将IX指针作为参数进行了传递;那么是否需要进行AddRef和Release呢?就像上面代码中我没有使用AddRef和Release一样,是不需要的。当我们进行传递参数时,会将pIX赋给pIX2,由于pIX2的生命周期在pIX的嵌套内,所以是不需要进行引用计数的。

总结上面的内容,当能找出那些生命周期嵌套在引用同一接口的指针生命周期内的接口指针时,就可以进行引用计数的优化。

引用计数使用规则

除了在《COM编程——引用计数(1)》总结的那三条规则之外,这里再进行一次最全面的总结。

  1. 输出参数规则
    输出参数指的是给函数的调用者传回一个值的函数参数。在函数体中将设置此输出参数的值。可以说,输出参数同函数的返回值是类似的。比如QueryInterface的第二个参数就是一个输出参数。任何在输出参数中或作为返回值返回一个新的接口指针的函数必须对此接口指针调用AddRef。这个和之前总结的那三条中的第一条是一致的;
  2. 输入参数规则
    输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值,但是不会修改它或将其返回给调用者。比如在上面的例子中,使用了pIX作为参数传递给了Foo函数了,pIX就是输入参数。就像上面说的那样,对传入函数的接口指针,不需要调用AddRef和Release,这是因为函数的生命周期嵌套在调用者的生命期内;
  3. 输入-输出参数规则
    输入-输出参数同时具有输入参数及输出参数的功能。在函数体中可以使用输入-输出参数的值,然后可以对可以对这些值进行修改并将其返回给调用者;
    在函数中,对于输入-输出参数传递进来的接口指针,在给它赋另外一个接口指针值之前一定更要记的调用其Release。在函数返回之前,还必须确保对输出参数中所保存的接口指针调用了AddRef;
  4. 局部变量规则
    对于局部复制的接口指针,由于它们只是在函数的生命周期内才存在,因此不需要调用AddRef和Release;
  5. 全局变量规则
    对于保存在全局变量中的接口指针,在将其传递给另外一个函数之前,必须调用其AddRef。这是因为这个变量是全局性的,所以任何函数都可以通过调用其Release来终止其生命期。对于保存在成员变量中的接口指针,也应该按照这个方法来进行处理,因为类中的任何成员函数都可以改变此接口指针的状态;
  6. 不能确定时的规则
    对于任何不能确定的情况,都要使用AddRef和Release。省略AddRef和Release只是带来了代码上的简洁和易懂,并不会对整个程序的性能或内存的分配带来什么改善;如果错误的省略了AddRef和Release,却有可能带来某个组件一直存在于内存中,无法被销毁。

有些地方不需要进行调用AddRef和Release时,最好加上必要的注释,这是一个好的编程习惯,便于后面维护该代码的人能更好的理解写代码人的意思。

总结

总结了那么多,总的说来就是AddRef告诉组件,我想使用某个组件了;Release去告诉组件,我使用完某个组件了,同时,Release也给组件提供了一些控制其生命周期的能力。好了,关于最重要的IUnknown接口就到此总结完成了,希望我的总结能给大家带来一些帮助。也希望大家和我分享你的学习心得。我坚信,分享使我们更加进步。

2014年1月4日 于大连,东软。

打赏

未经允许不得转载:果冻想 » COM编程——引用计数(2)

分享到:更多 ()

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

在这里玩技术,享受技术带来的疯狂

捐赠名单关于果冻