第二章 COM 对象和接口

34
第第第 第第第 COM COM 第第第第第 第第第第第 第第第第第第第 COM 第第 第第第 COM 第第第第第第第第 2.1 COM 第第 第 COM 第第第 第第第第 COM 第第第第第第第第第 COM 第 第第第第第第第第第第第第COM 第第第第第第第第第第第第第第第第第 第第第 第第第第第 体。 COM 第第第第第第第第第第 第 COM 第第 第第第第第第第第第第第第第第 ( 第第第第第第 ) 第第第第第第第第 COM 第第第第第第第第第第

description

第二章 COM 对象和接口. 这一章主要详细介绍 COM 规范,尤其是 COM 对接口使用的约定。 2.1 COM 对象 在 COM 规范中,并没有对 COM 对象进行严格的定义,但 COM 提供的是面向对象的组件模型, COM 组件提供给客户的是以对象形式封装起来的实体。客户程序与 COM 组件程序进行交互的实体是 COM 对象,它并不关心组件模块的名称和位置(即位置透明性),但它必须知道自己 COM 在于哪个对象进行交互。. COM 对象和接口. 2.1.1 COM 对象的标识--- CLSID - PowerPoint PPT Presentation

Transcript of 第二章 COM 对象和接口

Page 1: 第二章  COM 对象和接口

第二章 第二章 COMCOM 对象和接口对象和接口 这一章主要详细介绍 COM 规范,尤其是 COM对接口使用的约定。 2.1 COM 对象 在 COM 规范中,并没有对 COM 对象进行严格的定义,但 COM 提供的是面向对象的组件模型,

COM 组件提供给客户的是以对象形式封装起来的实体。客户程序与 COM 组件程序进行交互的实体是 COM 对象,它并不关心组件模块的名称和位置 ( 即位置透明性 ) ,但它必须知道自己COM 在于哪个对象进行交互。

Page 2: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.1.1 COM 对象的标识--- CLSID COM 的位置对客户程序来说是透明的,因为客户程序并不直接去访问 COM 组件,客户程序通过一个全局标识符进行对象的创建和初始化。 从可读性来考虑,用字符串是最简单的,可是这样却不能保证组件对象的唯一性。 也可采用 IP 地址标识法,用一个 32 位整数来建立这个全局标识符,可保证唯一,可是必须有一个专门的权威机构为 COM 组件分配整数标识符,对 Internet 是合理的,可对于 COM 则不可取。 COM 规范采用了 128 为全局唯一标识符 GUID ,

{54BF6567-1007-11D1-B0AA-444553540000}

Page 3: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口在 C/C++ 语言中可以用如下的结构来描述 typedef strut_GUID{ DWORD Data1; DWORD Data2; DWORD Data3; BTYE Data4[8];} GUID; 这样的话前面的 GUID 可定义为: extern “C” const GUID CLSID_MYSPELLCHECKER= {0x54bf6567,0x1007,ox11d1, {0xb0,0xaa,0x44,0x45,0x53,0x54,0x00,0x00}}; 空间 时间值

Page 4: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 COM 规范使用 GUID 来标识 COM 对象的思想源于

OSF(Open Software Foundation, 开放式软件基金会 ) 采用的 UUID(Universally Unique Identifier) , UUID 被定义为 DCE(Distributed Computing Environment ,分布式计算环境 ) 的一部分,主要用于标识 RPC(remote procedure call ,远程过程调用 ) 通信的双方。

手工来构造 128 位 GUID 或者编写程序来产生 GUID 比较麻烦。为此, Microsoft Visual C++ 提供了两个工具来实现: UUIDGen.exe 或 GUIDGen.exe ,前者是一个命令行程序,后者是一个基于对话框的应用程序。 COM 库提供了 API 函数来产生 GUID: HRESULT CoCreateGuid(GUID * pguid);

Page 5: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口2.1.2 COM 对象与 C++ 对象的不比较 1. 封装特性 数据封装是两者都有的特性,但形式不同。 在 COM 对象中,数据是完全封装在对象内部的,外部不可能直接访问对象的数据属性,因为 COM 对象和客户程序可能在不同的模块中甚至在不同的进程中或不同的机器上,因此,客户不能直接访问 COM 对象的属性。

Page 6: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口2. 可重用性 COM 对象的可重用性表现在 COM 对象的包容和聚合上,一个对象可以完全使用另一个对象的所有功能。 C++ 对象的可重用性表现在 C++ 类的继承性上,派生类可以调用其父类的非私有成员函数。

Page 7: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 C++ 对象还有一个多态性。 C++ 的多态性体现了 C++ 语言用来描述事务的高度抽象的特征,在 C++ 中对象的多态性需要通过其虚函数才能体现; COM 对象也有多态性,这种多态性是通过 COM对象所具有的接口来表现的。 2.2 COM 接口 COM 对象的客户与对象之间通过接口进行交互,所以组件之间接口的定义很重要,它也是 COM 规范的核心。 2.2.1 从 API 到 COM 接口 假如要实现一个字处理应用系统,它需要一个查字典的功能,按照组件程序设计的方法,把查字典的功能放到一个组件程序中实现最好,如果以后字典程序的查找算法或字典库改变了,只要接口没有变,新的程序仍能够运行。

Page 8: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 为了把应用系统和组件程序连接起来,又能使它们协同工作,就是先定义一组查字典的函数。 在字典组件程序中,定义的 API 函数如下: BOOL EXPORT Initialize(); BOOL EXPORT LoadLibrary(char *); BOOL EXPORT InsertWord(char *,char *); BOOL EXPORT DeleteWord(char *); BOOL EXPORT LookupWord(char *,char **); BOOL EXPORT RestoreLibrary(char *); void EXPORT FreeLibrary();

应用 A 或组件 A 应用 B或组件 B

字典组件

Page 9: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 平面型 API 接口层可以很好地把两个程序连接起来,但存在一下问题: 1. 当 API 函数非常多时,使用会非常不方便,需要对函数进行组织。 2. API 函数需要标准化,按照统一的调用方式进行处理,以适应不同的语言编程实现。 COM 定义了一套完整的接口规范,不仅可以弥补 API作为组件接口的不足,还充分发挥了组件对象的优势,并实现了组件的多态性。2.2.2 接口定义和标识 接口是包含一组函数的数据结构,通过这组数据结构,客户代码可以调用组件对象的功能。接口定义了一组成员函数,这组成员函数是组件对象暴露出来的所有信息,客户程序利用这些函数获得组件对象的服务。

Page 10: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 接口函数表通常被称为虚函数表 (virtual funtion table ,简称

vtable) ,指向 vtable 的指针为 pVtable 。 对于一个接口来说,它的 vtable 是确定的,因此接口成员函数个数是不变的,而且成员函数的先后顺序也是不变的;对其参数和返回值也是确定。在一个接口的定义中,所有这些信息都必须在二进制一级确定,不管什么语言,只要能支持这样的内存结构描述,就可以定义接口。 struct IDictionaryVtbl; struct IDictionary;{ IDictionaryVtbl *pVtbl;}; stuct IDictionaryVtbl{ BOOL (* Initialize)(IDictionary * this); BOOL (* LoadLibrary)(IDictionary * this,String);

void (* FreeLibrary)(Idictionary* this);};

Page 11: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 以上定义需要说明的几点如下: 1. 每一个接口成员函数的第一个参数为指向

Idictionary 的指针, 2. 在接口成员函数中,字符串变量必须用 Unicode字符指针, COM 规范要求使用 Unicode 字符,而且 COM 库中的 API 函数也使用 Unicode 字符。如果用 ANSI 字符,则必须进行转化。 3. 不仅成员函数的参数名是确定,而且应该使用相同的调用习惯。客户程序在调用成员函数之前,必须把参数压到栈中,然后再进入成员函数中,成员函数依次把参数从栈中取出,在函数返回之前或返回之后,必须恢复栈的位置。在 windows中,有两种调用习惯,分别为 _cdecl 和 _stdcall

( 或 pascal) 。

Page 12: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 4. 在 C 语言中,用这种结构只是描述了接口,并没有提供具体的 实现,对于客户程序,它只需要这样描述,就可以调用 COM 对象的接口;而对于组件程序,还必须提供具体的实现过程。 5. 从 C 语言的描述可以看出,由于 COM 接口的这种二进制结构,只要编程语言能够支持“ structure” 或“ record” 类型,并且这种类型能够包含双重的指向函数指针表的成员,就可用来编写 COM组件和使用它。 类似于 COM 对象的表示方法, COM 接口也采用了全局标识符 IID(interface identifier) 。例如: extern “C” const IID IID_IUnkouwn= {0x54bf6567,0x1007,ox11d1, {0xb0,0xaa,0x44,0x45,0x53,0x54,0x00,0x00}};

Page 13: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.2.3 用 C ++语言定义 jiek COM 接口结构中 vtable 与 C++ 中类的 vtable( 类的虚函数表 ) 完全一致,因此,用 class 描述 COM 接口是最方便的。 用 C++ 定义字典接口如下: class IDictionaryl{ BOOL Initialize)() = 0; BOOL LoadLibrary(String) = 0; BOOL InsertWord(String,String) = 0;void FreeLibrary() = 0;}; 因为 class 定义中隐藏了 vtable ,并且,每个成员函数隐藏了一个参数 this指针, this指针指向类的实例。

Page 14: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 COM 接口结构和 C++ 中类的内存结构完全一致。

接口指针 指针对象实现

指针函数 1指针函数 2

…………指针函数 3

pVtable vtable

接口结构

pVtable

BOOL Initialize(this *);......void FreeLibrary(this *);

this

IDictionary

vtable

C++ 中类的内存结构

Page 15: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 接口只是一种描述,如果 COM 对象要实现 IDictionary 接口,则

COM 对象必须以某种方式把它自身与 IDictionary联系起来,然后把 IDictionary 的指针暴露给客户程序,于是客户程序就可以调用该对象的字典功能。 当客户端获得某个字典对象的接口指针品品 pIDictionary 之后,她就可以调用该接口的成员函数。例如: pIDictionary->LoadLibrary(“Eng_ch.dict”); 如果使用 C 语言的 struct IDictionary ,则对接口成员函数的调用应如下: pIDictionary->pVtbl->LoadLibrary(“pIDictionary ,Eng_ch.dict”); 上述两种调用完全相同。

Page 16: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.2.4 接口描述语言 IDL COM 规范在采用 OSF 的 DCE 规范的描述远程调用接口 IDL 的基础上,进行扩展形成了 COM 接口的描述语言。接口描述语言提供了一种不依赖于任何语言的接口描述方法,因此,它可以成为组件程序和客户程序之间的共同语言。 其中不仅定义了 COM 接口,还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,可以指定每个参数的类型、输入输出特性,甚至支持变长度的数组描述。例如: interface IDictionaryl{ HRESULT Initialize)(); HRESULT LoadLibrary([in] String);HRESULT FreeLibrary();};

在 VC 中提供了 MIDL 工具,可以把 IDL 接口描述文件编译为C/C++兼容的接口描述头文件 (.h) 。

Page 17: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.2.5 接口的内存模型 COM 对象往往有自己的属性数据,这些属性数据反映了对象的状态,正是通过这些属性数据,才反映了对象的不同。例如,字典对象有一个字典数据表 m_pData 成员和字典文件名

m_DictFilename作为其基本的属性数据。用 C++ 语言实现如下: class Cdictionary:public Idictionary { public: ………… private: struct DictWord * m_pData; char * m_DictFilename[128]; ………… };

Page 18: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 按照类 Cdictionary 的定义,则接口 IDictionary 和字典对象的内存结构将变为:

客户使用的接口指针pIDictionary

pVtableCDIctionary 类中虚函数的具体实现

Intialize

LoadLibray

…………

InsertWord

vtable

接口 IDictionary 与字典对象属性之间的结构关系

m_pData

m_DictFilename

Page 19: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 如果一个客户使用了两个字典对象,则两个字典对象公用了成员函数,但数据属性不能公用,根据 C++ 的编译原理,内存结构如下:

客户使用的接口指针pIDictionary1

pIDictionary2

pVtable

CDIctionary 类中虚函数的具体实现

Intialize

LoadLibray

…………

InsertWord

vtable

多个字典对象与接口 IDictionary 之间的结构关系

m_pData

m_DictFilename

pVtable

m_pData

m_DictFilename

Page 20: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 如果第二个字典组件对象没采用 CDictionary 类的结构来实现其字典功能,但也实现了 Cdictionary 接口,则此时结构如下:

客户使用的接口指针pIDictionary1

pIDictionary2

pVtableCDIctionary 类中虚函数的具体实现

Intialize

LoadLibray

…………

InsertWord

vtable

不同方法实现的两个字典对象与接口 IDictionary 之间的结构关系

m_pData

m_DictFilename

pVtable

字典数据…………

Intialize

LoadLibray

…………

InsertWord

vtable

另一个字典对象类中虚函数的具体实现

Page 21: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 在以上给出的三个模型图中,每个接口成员函数都包含一个 this指针,通过 this指针,接口成员函数可以访问到字典对象的属性数据。按照 Ciictionary 的定义方法,该 this指针就是指向

Ciictionary 类的对象,因此在虚函数中可以直接访问 Ciictionary的数据成员。 并非一定要采取这种机制来定义接口,也可采用其它方法来定义,只要接口成员函数中的 this指针 ( 接口指针 ) 与对象数据能建立确定的连接,在进口成员函数中可以访问到对象数据即可。例如,

VC 的 MFC 库和 ATL(active template library ,活动模板库 ) 模板库分别采用了不同的机制来提供对 COM 接口的支持。

Page 22: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.2.6 接口的一些特性 1. 二进制特性2. 接口不变性 3. 继承性 接口的继承与类的继承不同。 a. 类继承不仅是说明继承,也是实现继承,既派生类可以继承基类的函数实现,而接口继承只是说明继承,即派生的接口只继承基接口的成员函数说明,并没有继承基接口的实现,因为接口定义布包含函数 实现部分。 b. 类继承允许多重继承,一个派生类可以有多个基类,但接口只允许单继承,不允许多重继承。

Page 23: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 4. 多态性---运行过程的多态性 COM 的多态性体现在接口上,多态性使得客户程序可以用统一的 方法处理不同的对象,甚至是不同类型的对象,只要它们实现了同样的接口。如果几个不同的 COM 对象实现了同一个接口,则客户程序可以用同样的代码调用这

个 COM 对象。 COM 规范允许一个对象实现多个接口,因此,

COM 对象的多态性可以在每个接口上得到体现。正是由于 COM 的多态性,才可以用 COM规范建立插件系统,应用程序可以用通用的方法处理每个插件。

Page 24: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.3 IUnknown 接口 COM 定义的每个接口都必须从 IUnknown 继承过来, 主要是因为

IUnknown 接口提供可两个非常重要的特性 : 生存控制和接口查询。 生存控制: 接口查询:

Page 25: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 IUnknown 接口的定义 (IDL) : interface IUnknown { HRESULT QueryInterface([in], REFIID iid,[out] void ** ppv); ULONG AddRef(void); ULONG Release(void);} 为了便于理解和对照,给出 IUnknown 接口的 C++ 定义形式: class IUnknown { public: virtual HRESULT _stdcall QueryInterface(const IID& iid,void **

ppv)=0; virtual ULONG _stdcall AddRef()=0; virtual ULONG _stdcall Release()=0;} QueryInterface 函数用来查询其它接口指针, AddRef 和 Release 函数用来对引用计数进行操作。

Page 26: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.3.1 引用计数 1. COM 对象只实现了一个接口,例如为 ISomeInterface 。 因为 ISomeInterface 接口继承与 IUnknown 接口,因此

ISomeInterface 接口中的成员函数包含 Iunknown 的三个函数。一个使用该 COM 对象的客户程序通过某种途径调用获得了该接口的接口指针 pSomeInterface ,并且,客户程序在许多逻辑模块中都用到了该 COM 对象,从而在客户程序的很多地方都保持了对该接口指针的引用,比如说有三个地方分别用 pSomeInterface1 ,pSomeInterface2 , pSomeInterface3指向该接口的指针。

在客户程序的这三个模块中,它可以调用接口成员函数获得接口所提供的服务,如果它一直需要该接口所提供的服务,那它就需要控制该接口对象使它一直保持在内存中;如果不再需要,就应该通知接口不再需要服务了。由于每个模块并不知道其它模块是否在使用 COM 对象,只知道自己还用没用。而对 COM 对象来说,只要有任一一个模块还在用它,它就必须驻留在内存中,不能释放。

Page 27: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 COM 采用了“引用计数”技术来解决内存的管理问题, COM 对象通过引用计数来决定是否继续生存下去。每个 COM 对象都记录了一个称为“引用计数”的数值,该数还有表示有多少个有效的对象在引用这个 COM 对象。其中主要是通过对这个“引用计数”进行加 1 和减 1 来对 COM 对象的生存进行控制的。客户的到了一个指向该对象的指针时就加 1 ,不用后就减 1 。当为 0 时,从内存中释放该 CON 对象。对接口进行复制时 ( 调用拷贝构造函数 ) ,也加 1 。 IUnknown 接口成员函数 AddRef 和 Release 分别完成引用计数的加 1 和减 1 。 2. 如果一个 COM 对象实现了多个接口,则可以采用同样的技术,只要引用计数不为 0 ,就表明该 COM 对象的客户还在使用它。 通过引用计数 COM 对象的客户程序可以通过接口指针很好地控制对象的生存期。

Page 28: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2.3.2 实现引用计数 按照 COM 规范,一个 COM 组件可以实现多个 COM 对象,每个

COM 对象又可支持多个 COM 接口。因此可有一下选择: 在 COM 组件一级实现引用计数。 在 COM 对象一级实现引用计数。 在 COM 接口一级实现引用计数。 1. 设置一个针对整个组件全局的引用计数。在实现组件时,我们用一个全局整数变量记录引用计数,当组件被初始化装入内存时,该计数为 0 ;对象被创建时,计数值开始增加,在整个组件被使用过程中,计数值一直保持大于 0 ,当组件中地对象都被用完后,计数值应该减回到 0 ,于是组件模块就可以从内存中卸出。 这种引用计数可以控制组件模块的生存周期,但控制不了 COM对象的生存与否。如果一个组件在运行过程中产生了两个 COM对象,不管是同类还是不同类,当某个对象减 1 时,由于引用是全局的,是在全局引用计数中减 1 ,因此还不能判断是否这个对象已经不再用了,必须等到所有对象释放完后,也就是这个引用计数为 0 时,才可进行释放。这样资源的利用效率就降低了。称之为“计数分辨率太粗”

Page 29: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 2. 为每个 COM 对象设置一个引用计数。当 COM 对象被创建时,计数值开始从 0增加,只要对象还在被客户程序使用,则这个计数值就大于 0 ;当不用时,就减回 0 ,然后就可以释放掉。 这样可以有效的管理多个对象的组件程序,但每个对象被释放掉之后,它必须通知组件程序,组件程序发现没有对象存在时,在就可把组件模块从内存中进行卸掉。因此,组件程序应该保持一份有效对象的记录,可以用一个全局的对象计数值来控制组件的生存周期。当对象释放时减 1 ,为 0 时释放。 3. 为每个接口设置一个引用计数。因为客户通过接口指针与组件对象进行通信,所以为每个接口设置引用计数可以跟踪客户对

COM 对象的使用情况。在对对象进行调用过程中,并非调用所有的接口,这时与那些不用的接口相关的资源就可以不被占用。 这样做对调试组件程序和分析客户程序的使用情况非常有帮助,可以有效的管理每个接口的使用情况。可是在减到 0 时,接口就要通知对象,对象要判断是否所有接口都减到 0 ,如果是,对象释放,然后对象又要通知组件,组件又要去判断,如果所有对象减到 0 ,组件就被释放。这有点“计数分辨太细”。

Page 30: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 在对象一级实现引用计数可选择全局变量;在对象一级实现引用计数可通过成员变量来实现。在接口一级实现引用计数,可通过为对象实现的每个接口设置一个类成员变量作为引用计数变量。综合来看,在对象一级实现引用计数以控制对象和组件的生存周期比较合理。 CDictionary 的成员函数 AddRef 和 Release就实现了在对象一级的引用计数,这两个函数以及这个类的构造函数如下: CDictionary:: CDictionary(){ m_ref=0; //……} ULONG CDictionary:: AddRef(){ m_ref++; return(ULONG) m_ref} ULONG CDictionary:: Release(){ m_ref--; if(m_ref==0){ delete this; return 0;} Return(ULONG) m_ref;}

Page 31: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 按照引用计数的原理,可以制定最基本的客户控制规则: 1. 客户创建了组件对象并获得了第一个接口指针后,引用计数应该是 1 。 2. 在客户程序中,当把接口指针赋给其它变量时,应该调用

AddRef ,使引用计数加 1 。 3. 在客户程序中,当一个接口指针被使用完之后时,应该调用

Release ,使引用计数减 1 。按照以上规则,可给出客户代码如下( 伪 C++代码 ) :

// 产生一个新的字典对象 IDictionary * p IDictionary=CreateObject(……); if (pIDictionary==NULL) return; // 如果成功,引用计数为 1 Bool retValue= pIDictionary->LoadLibrary(“1.dict”); if(retValue==FALSE){ pIDictionary->Release(); return;}

Page 32: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 …… IDictionary * pIDictionaryForword= pIDictionary; pIDictionaryForWord->AddRef(); pIDictionaryForWord->InsertWord(“……”,”……”); pIDictionaryForWord->DeleteWord(“……”); pIDictionaryForWord->Release(); …… pIDictionary->Release(); // 最后释放字典对象2.3.3 使用引用计数规则 下面是一个完整的引用计数规则,这将使客户模块之间写作使用组件对象更趋于一般化,其中分不同场合使用或传递接口指针变量进行分类,给出相应规则如下: 1. 函数的参数中使用接口指针变量。分 in 、 out 和 inout ,分处理如下: a.输入参数。此参数值在调用过程中不会变,多数语言中,此参数为传值参数或为常数。由于此参数由调用函数控制,因此在被调用函数执行过程中,接口指针一直保持有效。所以,在被调用函数中,不必调用 AddRef 和 Release 函数。

Page 33: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 b.输出参数。此参数在被调用过程中进行赋值,而且被调用 函数并没有用到函数初始化传进来的值,此参数相当于函数的一个返回值。输出参数相当于在被调用函数中生成了一个新的接口指针变量,因此,在被调用函数返回之前,对此参数应该调用 AddRef使接口引用计数加 1 。 c.输入、输出参数。对这种参数采用的规则是:在参数被修改前,对原来传进来的接口指针调用 Release 以使引用计数减 1 ,在参数被修改之后,对新的接口指针变量调用 AddRef ,以标记对新的接口指针的引用。如果在函数执行过程中,参数没有被修改,则类似于 in ,即不加 1 ,又不减 1 。 2. 局部接口指针变量。如果在一个局部函数块中,一个局部接口指针变量被赋予值并调用了接口成员函数,则对该局部接口指针变量可以不调用 AddRef 和 Release ,因为在这个局部函数块中,接口指针总是有效的。 3. 全局接口指针变量。因为任何一个函数都可以访问全局接口指针变量,所以在把全局接口指针作为输入参数传给某个函数之前,应该调用 AddRef 以保证在函数中可以使用该接口指针变量,因为它是全局变量,其它的函数有可能回调用 Release 函数。在函数返回之后应调用 Release 。

Page 34: 第二章  COM 对象和接口

COMCOM 对象和接口对象和接口 4. C++ 中类成员变量为接口指针变量。因为对于类的作用域来讲,成员变量相当于全局变量,在类的所有成员函数中都可以访问此变量,因此规则 3 也适用于类成员变量的情形。 5. 当以上情况都不适合时,可使用以下一般性规则: a. 在顺序执行过程中,如果要对一个接口指针变量赋值,赋值后 就要调用 AddRef ,并且,在赋值前此接口指针还没有结束,则赋值前必须对它调用 Release 以便先结束它的使用。 b. 如果要结束一个接口指针变量,以后不再用到它了,则调用

Release 函数。 在某些特殊条件下,可以省略对 AddRef 和 Release 的调用。 使用引用计数主要可以防止两方面的问题:当使用一个接口指针变量时,发现它所指向的 COM 对象已经不存在了。;当使用完

了 COM 对象后,对象并不被清除。对于前者,可能忘了AddRef ,导致出错;对于后者,可能忘了 Release ,导致资源不释放。