COM引用计数规则
1. AddRef/Release 规则【1】
COM 的引用计数规则可以精简为以下三个简单的公理:
1、当一个非空的接口指针从一个内存位置被拷贝到另一个内存位置之前,应该要调用 AddRef,以便通知对象又有附加的引用发生了。
2、对于已经包含非空接口指针的内存位置来说,在重写该内存位置之前,必须要先调用 Release,以便通知对象这个引用已经被销毁了。
3、如果你对于两个或多个内存位置之间的关系有特殊的理解的话,那么多余的 AddRef 和 Release 可以被优化掉。
根据以上三条公理,我们可以把他变成实际的编程指导。
下面是一些比较通用的、要求调用 AddRef 方法的情形:
A1、当把一个非空接口指针写到局部变量中时。
A2、当被调用方把一个非空接口指针写到方法或者函数的实际结果时。
A3、当被调用方返回一个非空接口指针作为函数的实际结果时。
A4、当把一个非空接口指针写到对象的一个数据成员中时。
下面是一些比较通用的、要求调用 Release 方法的情形:
R1、在改写一个非空局部变量或者数据成员之前。
R2、在离开非空局部变量的作用域 (scope) 之前。
R3、当被调用方要改写方法或者函数的 [in,out] 参数,并且参数的初始值为非空时。
R4、在改写一个对象的非空数据成员之前。
R5、在离开一个对象的析构函数之前,并且这时还有一个非空接口指针作为数据成员。
还有一种很常见的特殊情况是,当把接口指针作为 [in] 参数传给函数时,可以适用前面给出的第3条关于内存位置之间的关系有特殊理解的规则:
S1、当调用方把一个非空接口指针通过 [in] 参数传给一个函数或者方法时,既不需要调用 AddRef,也不需要调用 Release,因为在调用堆栈中,临时变量的生命周期只是“用于初始化形式参数”的表达式的生命周期的一个子集。
这十条指导规则几乎涵盖了 COM 编程过程中屡屡出现的各种情况。
PS: 请尽量使用 COM 智能指针。可以避免因为疏忽或异常而绕过了 Release,引起泄露。
《COM 本质论》 Essential COM 中文版 44页
DON BOX 著 潘爱民 译
2. 什么时候AddRef()?
D3D是 COM组件,它在服务进程中运行,而不在当前的客户进程中。在DX组件运行过程中,要创建一系列接口对象,如CreateDevice()返回接口指针,这些接口及其占用内存什么时候释放,要通过“引用计数”的技术来解决。AddRef()给这个接口指针的计数加1,而Release()会将之减1。一旦减到0,表示没有客户使用了,相关的接口就释放了。由此可知,每次调用Rlease()后,并不一定会释放内存,而是当引用计数归0时释放内存。
这样,对接口指针的使用,就像维护堆栈的平衡一样,要仔细,而且按照某种约定规则使用。
但平时D3D编程中,怎么不用AddRef()呢?这是由于一个接口指针,如ID3DDevice,或VertexBuf指针,都是 D3DXCreate出来的,在Create时候,在内部已经事先AddRef()了,你就不需要再做这工作了。只要你在不用时,调用 p指针 ->Relase()就释放了。一般编程,特别是小型示例程序,都是初始化时建立一次,关闭时释放,都遵守了这种约定,所以不存在这种问题。
但在CreateMeshContainer()函数中,以多种方式使用了指针,在局部指针变量中来回传递,所以问题复杂化了。在COM编程中约定,任何时候地接口指针赋值(复制),都要AddRef(),在指针变量结束生命期前,再Release(). 但许多程序员都不是严格这么做。因为在局部变量用完就废了,先AddRef()增加计数再Release()减少,和直接使用最后是等效的。几乎是多此一举。这与编程习惯有关系。一旦引用计数不对,如果没有统一的习惯,不好排查。在CreateMeshContainer()中,对接口指针的使用有三种方式,例举如下:
方式一:不使用AddRef()。和普通指针一样,临时变量是左值,接口指针是右值,直接赋值使用。如:
pMesh = pMeshData->pMesh;
这是由于pMesh是局部变量,它只是临时引用一下,没必要为它先AddRef(),后Release()。
方式二:隐式的使用AddRef()。由于用到了一些内部有AddRef()动作的函数,就要按照COM约定,在子程序结束前Release()
pMesh->GetDevice(&pd3dDevice);//此处d3d设备引用计数已经加1
....
SAFE_RELEASE(pd3dDevice);//--此处将引用计数减1,并不是真的释放d3d设备
在本例中,pd3dDevice在GetDevice()中已经Addref()过了,所以,在退出CreateMeshContainer()前,必须pd3dDevice->Release()
方式三:显式的使用AddRef()。如果一个指针值,不是由D3DXCreate出来的,而是通过赋值方式复制给一个全局变量或长期变量的。所以,可以通过AddRef()的方式来延迟该对象的释放。因为,如果不AddRef(),极有可能在函数返回该对象就可能释放了。它就像一个加油站,使得传入对象的寿命延长至自己控制范围内。用了AddRef(),就要在相关的Destroy中添加Release()。
Reference:
【1】COM 的引用计数规则 AddRef/Release 规则(http://www.hoboss.com/blog/archives/420)
【2】COM组件里的AddRef() (http://www.cnblogs.com/wonderKK/archive/2011/11/07/2240281.html)
浙公网安备 33010602011771号