参考网站:https://www.cnblogs.com/fangyukuan/archive/2010/06/06/1752621.html

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;
}
 

本文地址:http://www.cnblogs.com/fangyukuan/archive/2010/06/06/1752621.html

COM笔记-引用计数的更多相关文章

  1. swift学习笔记5——其它部分(自动引用计数、错误处理、泛型...)

    之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...

  2. 【读书笔记】iOS-ARC-环境下如何查看引用计数的变化

    一,新建立一个工程,用于测试引用计数的变化. 二,找到如下路径Build Phases---->Compile Sources---->AppDelegate.m 三,选中AppDeleg ...

  3. swift学习笔记之-自动引用计数

    //自动引用计数 import UIKit /*自动引用计数(Automatic Reference Counting) 防止循环强引用 Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用 ...

  4. 初步swift语言学习笔记6(ARC-自己主动引用计数,内存管理)

    笔者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/31824179 转载请注明出处 假设认为文章对你有所帮助.请通过留言 ...

  5. swift学习笔记(七)自己主动引用计数

    与Object-c一样,swift使用自己主动引用计数来跟踪并管理应用使用的内存.当实例不再被使用时,及retainCount=0时,会自己主动释放是理所占用的内存空间. 注:引用计数仅适用于类的实例 ...

  6. c++沉思录 学习笔记 第六章 句柄(引用计数指针雏形?)

    一个简单的point坐标类 class Point {public: Point():xval(0),yval(0){} Point(int x,int y):xval(x),yval(y){} in ...

  7. 【读书笔记】iOS-ARC-环境下怎样查看引用计数的变化

    一.新建立一个project.用于測试引用计数的变化. 二,找到例如以下路径Build Phases---->Compile Sources---->AppDelegate.m 三,选中A ...

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

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

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

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

随机推荐

  1. [刘阳Java]_Spring IoC原理_第2讲

    Spring IoC(DI)是Spring框架的核心,所有对象的生命周期都是由它们来管理.对于弄懂Spring IOC是相当关键,往往我们第一次接触Spring IOC大家都是一头雾水.当然我们这篇文 ...

  2. 五个 .NET 性能小贴士

    原文:bit.ly/3wSpO4o 作者:Nikita Starichenko 翻译:精致码农 大家好!今天我想和大家分享几个 .NET 的性能小贴士与基准测试. 我的系统环境: BenchmarkD ...

  3. JDBC连接流程

    ConectionFactory cf = new ConectionFactory();//创建数据库连接Connection con = cf.getConection();//打开水数据库的连接 ...

  4. 共享内存 & Actor并发模型哪个更快?

    HI,前几天被.NET圈纪检委@懒得勤快问到共享内存和Actor并发模型哪个速度更快. 前文传送门: 说实在,我内心10w头羊驼跑过...... 先说结论 首先两者对于并发的风格模型不一样. 共享内存 ...

  5. videojs文档翻译Guides-components

    components Components Video.js播放器的架构围绕组件. Player类和所有表示播放器控件和其他UI元素的类都继承自Component类. 这种架构使得可以轻松地以反映DO ...

  6. synchronized 加锁 this 和 class 的区别!

    synchronized 是 Java 语言中处理并发问题的一种常用手段,它也被我们亲切的称之为"Java 内置锁",由此可见其地位之高.然而 synchronized 却有着多种 ...

  7. 某学院m3u8视频解密获取分析实战分享

    [免责声明]本文来源于作者个人学习整理,仅供学习交流使用,不构成商业目的.所有资源均系本人个人学习或网络收集,仅提供一个展示.介绍.观摩学习的博文,不对其内容的准确性.可靠性.正当性.安全性.合法性等 ...

  8. C语言复习(一)

    类型为void*的指针代表对象的地址,而不是类型 如果需要使用另一个源文件中定义的变量,那么只需要在定义变量前加上extern关键字 ex: extern int x;//x在其他文件中定义 左值表达 ...

  9. 记一次 GitLab 的迁移过程

    目录 1. 迁移背景 2. GitLab 整体架构介绍 3. GitLab 安装 配置选择 安装方式选择 安装的网络区域 安装 GitLab GitLab 常用命令 配置管理员账号密码 4. 配置 G ...

  10. 庆FastGithub加入.NET Core Community

    .NET Core Community .NET Core Community是一个基于并围绕着 .NET 技术栈展开组织和活动的非官方.非盈利性的民间开源社区,提供了很多优秀的 .NET 开源项目. ...