COM编程之四 引用计数
【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编程之四 引用计数的更多相关文章
- obj-c编程11:内存管理和ARC(自动引用计数)
乖乖隆地洞,这篇文章内容可是不得了,内存管理哦!首先,这个要是搞不明白,你就等着进程莫名其妙的挂死,或是疯狂申请内存却不释放,结果被OS杀死,不管是"自杀"还是"他杀&q ...
- iOS开发--引用计数与ARC
以下是关于内存管理的学习笔记:引用计数与ARC. iOS5以前自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于代替之前的手工引用计数MRC(Manual Refer ...
- Objective-C内存管理之-引用计数
本文会继续深入学习OC内存管理,内容主要参考iOS高级编程,Objective-C基础教程,疯狂iOS讲义,是我学习内存管理的笔记 内存管理 1 内存管理的基本概念 1.1 Objective-C中的 ...
- 第3月第2天 find symbolicatecrash 生产者-消费者 ice 引用计数
1.linux find export find /Applications/Xcode.app/ -name symbolicatecrash -type f export DEVELOPER_DI ...
- ATL是如何实现线程安全的引用计数和多线程控制的
ATL是如何实现线程安全的引用计数和多线程控制的 正如标题所示,这是我经常被问到的一个问题,而每次我都从头开始给人说一次,其实说来过程理解起来的确有点复杂. 我们的每一个ATL Server Obje ...
- 引用计数 vs. GC
内存管理问题 内存管理是编程过程中的一个经典问题,早期在 C 语言时代,几乎都靠 malloc/free 手动管理内存.随着各个平台的发展,到现在被广泛采用的主要有两个方法: 引用计数 (ARC,Au ...
- ARC————自动引用计数
一.内存管理/引用计数 1.引用计数式内存管理的方式(下面四种) 对象操作 OC方法 生成并持有对象 alloc/new/copy/mutableCopyd等方法 持有对象 retain方法 释放对象 ...
- [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到)
原文:[推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到) [推荐]ORACLE PL/SQL编程之四: 把游标说透(不怕做不到,只怕想不到) 继上两篇:ORACLE PL ...
- (转)C++——std::string类的引用计数
1.概念 Scott Meyers在<More Effective C++>中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里 ...
随机推荐
- web系统权限设计
应该有七张表 1.appSystem 表: 主要在多系统中的 统一权限管理:主要就是系统的URL USE [Star_Permission] GO /****** 对象: Table [dbo].[A ...
- iOS 使用封装的NSLog来打印调试信息
//DLog代替NSLog //debugMethod() 代替 NSLog(@"%s", __func__) //DLog在release下不会输出 #ifdef DEBUG # ...
- iOS基础
1.属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用? readwrite 是可读可写特性:需要生成getter方法和s ...
- LeetCode Perfect Squares
原题链接在这里:https://leetcode.com/problems/perfect-squares/ 题目: Given a positive integer n, find the leas ...
- HTML5所有标签汇总按功能
基础 格式 表单 框架 图像 音频/视频 链接 列表 表格 样式/节 元信息 编程1. 基础(9个)<!DOCTYPE><html><title><body& ...
- iOS:城市级联列表的使用
1.介绍: 现在越来越多的项目都用到了地址,尤其是电商O2O的购物平台,我之前做的教育产品和电商产品都用到了,而实现地址的设置用到的技术就是城市级联列表,即普遍的做法就是自定义选择器控件UIPicke ...
- saltstack之(九)配置管理源码部署Nginx
场景:rpm包安装的nginx服务,无法满足定制模块的需求,故线上环境使用nginx源码进行安装.本片文章详细介绍如何使用saltstack的配置管理功能实现nginx软件的源码安装. 下载源码:pc ...
- MVC项目实践,在三层架构下实现SportsStore-04,实现分页
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- Enum 绑定到 RadioButton
参考 这篇文章. 和 http://www.cnblogs.com/626498301/archive/2012/05/22/2512958.html
- django 分页(2) 使用类 页码显示
django 分页显示页码 views.py 显示11页码 ) < 起始位置 - 10总页数 else 总页数 > IF 当前页 小于 起始位置 结束页 IF 当前页 大于 IF 如果结束 ...