【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函数。

以上两条代码如下:

 //Create a new component
IUnknown* pLUnknown = CreateInstance();
//Get interface IX
IX* pIX = NULL;
HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
pIX->fx(); //Use Interface IX
pIX->Release();
}
pLUnknown->Release();

(3)在赋值之后调用AddRef。

在将一个接口指针赋给另外一个接口指针时,应调用AddRef。

换句话说,在建立接口的另外一个应用之后应增加相应组件的应用计数。

代码如下:

 //Create a new component
IUnknown* pLUnknown = CreateInstance();
//Get interface IX
IX* pIX = NULL;
HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
pIX->fx(); //Use Interface IX
IX* pIX2 = pIX; //Make a copy of pIX pIX2->AddRef();
pIX2->fx(); //Do something
pIX2->Release(); pIX->Release();
}

【4】引用计数的完整示例

代码如下:

 //
// RefCount.cpp
// To compile, use: cl RefCount.cpp UUID.lib
//
#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() = ;
} ; interface IY : IUnknown
{
virtual void __stdcall Fy() = ;
} ; interface IZ : IUnknown
{
virtual void __stdcall Fz() = ;
} ; //
// 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() {} // 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+ << '.' << endl ;
return InterlockedIncrement(&m_cRef) ;
} ULONG __stdcall CA::Release()
{
cout << "CA: Release = " << m_cRef- << '.' << endl ; if (InterlockedDecrement(&m_cRef) == )
{
delete this ;
return ;
}
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 ;
}
//Output
/*
Client: Get an IUnknown pointer.
CA: AddRef = 1.
Client: Get interface IX.
CA QI: Return pointer to IX.
CA: AddRef = 2.
Client: Succeeded getting IX.
Fx
CA: Release = 1.
Client: Get interface IY.
CA QI: Return pointer to IY.
CA: AddRef = 2.
Client: Succeeded getting IY.
Fy
CA: Release = 1.
Client: Ask for an unsupported interface.
CA QI: Interface not supported.
Client: Could not get interface IZ.
Client: Release IUnknown interface.
CA: Release = 0.
CA: Destroy self.
*/

【5】引用计数的优化

正确使用引用计数规则(3)的示例代码做优化如下:

 //Create a new component
IUnknown* pLUnknown = CreateInstance();
//Get interface IX
IX* pIX = NULL;
HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
pIX->fx(); //Use Interface IX
IX* pIX2 = pIX; //Make a copy of pIX // pIX2->AddRef(); //unnecessary!!!
pIX2->fx(); //Do something
// pIX2->Release(); //unnecessary!!! pIX->Release();
}

关键是找出那些生命期嵌套在引用同一接口指针生命期内的接口指针。

 void Fun(IX* pIx)
{
pIx->Fx();
} void main()
{
IUnknown* pLUnknown = CreateInstance();
//Get interface IX
IX* pIX = NULL;
HRESULT hr = pLUnknown->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
Fun(pIX);
pIX->Release();
}
}

很显然,Fun的生命期包含在pIX的生命期中,因此对于传递给Fun的接口指针,无需调用AddRef和Release。

在函数中,不必要对存在于局部变量的接口指针进行引用计数。

因为局部变量的生命期同函数的生命期是一样的,因此也将包含在调用者的生命期内。

但当从某个全部变量或向某个全局变量复制一个指针时,则需要对此进行引用计数。

因为全局变量可以从任意函数中的任意地方被释放。

【6】引用计数规则

(1)输出参数规则

输出参数指的是给函数的调用者传回一个值的函数参数。比如QueryInterface函数

(2)输入参数规则。比如上面引用计数优化的例子。

(3)输入输出参数规则

对于输入输出参数传递进来的接口指针,必须在给它赋另外一个接口指针之前调用其Release。

在函数返回之前,还必须对输出参数中所保存的接口指针调用AddRef。示例代码如下:

 void ExchangeForCachedPtr(int i, IX**ppIX)
{
(*ppIX)->Fx();
(*ppIX)->Release();
*ppIX = g_cache[i];
(*ppIX)->AddRef();
(*ppIX)->Fx();
}

(4)局部变量规则

(5)全局变量规则

(6)不能确定时的规则

对于任何不能确定的情形,都应调用AddRef 和 Release对。

总而言之,通过引用计数,客户可以控制接口的生命期,而组件本身可以决定何时将它从内存中删除。

Good  Good Study, Day Day Up.

顺序  选择  循环  总结

COM编程之四 引用计数的更多相关文章

  1. obj-c编程11:内存管理和ARC(自动引用计数)

    乖乖隆地洞,这篇文章内容可是不得了,内存管理哦!首先,这个要是搞不明白,你就等着进程莫名其妙的挂死,或是疯狂申请内存却不释放,结果被OS杀死,不管是"自杀"还是"他杀&q ...

  2. iOS开发--引用计数与ARC

    以下是关于内存管理的学习笔记:引用计数与ARC. iOS5以前自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于代替之前的手工引用计数MRC(Manual Refer ...

  3. Objective-C内存管理之-引用计数

    本文会继续深入学习OC内存管理,内容主要参考iOS高级编程,Objective-C基础教程,疯狂iOS讲义,是我学习内存管理的笔记 内存管理 1 内存管理的基本概念 1.1 Objective-C中的 ...

  4. 第3月第2天 find symbolicatecrash 生产者-消费者 ice 引用计数

    1.linux find export find /Applications/Xcode.app/ -name symbolicatecrash -type f export DEVELOPER_DI ...

  5. ATL是如何实现线程安全的引用计数和多线程控制的

    ATL是如何实现线程安全的引用计数和多线程控制的 正如标题所示,这是我经常被问到的一个问题,而每次我都从头开始给人说一次,其实说来过程理解起来的确有点复杂. 我们的每一个ATL Server Obje ...

  6. 引用计数 vs. GC

    内存管理问题 内存管理是编程过程中的一个经典问题,早期在 C 语言时代,几乎都靠 malloc/free 手动管理内存.随着各个平台的发展,到现在被广泛采用的主要有两个方法: 引用计数 (ARC,Au ...

  7. ARC————自动引用计数

    一.内存管理/引用计数 1.引用计数式内存管理的方式(下面四种) 对象操作 OC方法 生成并持有对象 alloc/new/copy/mutableCopyd等方法 持有对象 retain方法 释放对象 ...

  8. [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到)

    原文:[推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到) [推荐]ORACLE PL/SQL编程之四: 把游标说透(不怕做不到,只怕想不到) 继上两篇:ORACLE PL ...

  9. (转)C++——std::string类的引用计数

    1.概念 Scott Meyers在<More Effective C++>中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里 ...

随机推荐

  1. Java学习-016-CSV 文件读取实例源代码

    上文(CSV文件写入)讲述了日常自动化测试过程中将测试数据写入 CSV 文件的源码,此文主要讲述如何从 CSV 文件获取测试过程中所需的参数化数据.敬请各位小主参阅,若有不足之处,敬请大神指正,不胜感 ...

  2. search支持多种标签

    织梦的搜索页面支持dede标签的方法一 打开文件:include/arc.searchview.class.php 找到: require_once(DEDEINC."/taglib/hot ...

  3. 一些常用的linux命令

    查看本机ip地址 ifconfig ln -s phpmyadmin 添加链接 /var/www/  与html  同级 打开本机电脑硬盘: cd /media/用户名 文件夹的新建命令: sudo ...

  4. Foundation of 3D computer Graphics--Reading notes

    2.1 几何数据类型 向量表示两个点之间的移动,点表示位置. 2.2 向量,坐标向量和基 向量$\overrightarrow{v}$ ,坐标向量c,基向量$\overrightarrow{b^{t} ...

  5. Linux就这个范儿 第19章 团结就是力量 LSB是Linux标准化基地(Linux Standards Base)的简称

    Linux就这个范儿 第19章 团结就是力量  LSB是Linux标准化基地(Linux Standards Base)的简称 这个图片好可爱,它是LSB组织的图标.你肯定会问:“图标这么设计一定有说 ...

  6. 发现EF中字段错误

    在使用EF时,报错: 对一个或多个实体的验证失败.有关详细信息,请参见“EntityValidationErrors”属性 添加一个验证方法: 代码: using System; using Syst ...

  7. :before 和 :after 的内幕 以及伪类 ( 转 )

    原文链接    http://www.cnblogs.com/zhujl/archive/2012/05/08/2489411.html ------------------------------- ...

  8. 证明 logX < X 对所有 X > 0 成立

    题目取自:<数据结构与算法分析:C语言描述_原书第二版>——Mark Allen Weiss       练习1.5(a)  证明下列公式: logX < X 对所有 X > ...

  9. PostgreSQL Monitor pg_activity

    PostgreSQL Monitor pg_activity Command line tool for PostgreSQL server activity monitoring. https:// ...

  10. 关于prototype

    之前听过课,可是这一块没怎么听懂,最近练了两个例子,又问了问小石同学,朦朦胧胧,感觉还是不太懂,记录点心得 最基本的例子 function Box(name,age){ this.name=name; ...