三. 从 c++ 到 com

48
1 三. 三C ++三 COM 1. 三三三三三三三三 2. 三三三三三 3. 三三三三三三三三三三 4. 三三三三三三三三三三三 1. 三三三三 2. 三三三三 3. 三三三三三三三三三三三三 5. 三三三三三三 1. 三三三三三三三 2. 三三 三三三三 一: 3. 三三三 三三三 4. 三三三三三三三 6. 三三三三

Upload: kasia

Post on 13-Jan-2016

153 views

Category:

Documents


1 download

DESCRIPTION

三. 从 C++ 到 COM. 源代码形式的重用 动态链接库 接口类与实现类的分离 抽象基类作为二进制接口 实现方式 内存泄漏 接口类与实现类的内存结构 对象的扩展性 功能扩展的需求 方法一:扩展接口 方法二:多接口 中性的类型转换 资源管理. 1 源代码形式的重用. C++ 的一个主要目标:用户自定义类型 UDT 并且可以在其他环境下重用。 类库市场 OWL MFC ATL VCL Qt 主要问题: 白盒子,客户程序和类库之间过度的耦合 重用性的主要障碍 编译与链接模型 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 三.  从 C++ 到 COM

1

三 . 从 C ++到 COM

1. 源代码形式的重用2. 动态链接库3. 接口类与实现类的分离4. 抽象基类作为二进制接口

1. 实现方式2. 内存泄漏3. 接口类与实现类的内存结构

5. 对象的扩展性1. 功能扩展的需求2. 方法一:扩展接口3. 方法二:多接口4. 中性的类型转换

6. 资源管理

Page 2: 三.  从 C++ 到 COM

2

1 源代码形式的重用 C ++的一个主要目标:用户自定义类型 UDT 并且可以在其他

环境下重用。 类库市场 OWL MFC ATL VCL Qt 主要问题 :

– 白盒子,客户程序和类库之间过度的耦合 – 重用性的主要障碍 编译与链接模型

以下各节将演示 C++ 的对象模型在“构造可重用的组件”时所遇到的挑战 .

Page 3: 三.  从 C++ 到 COM

3

一个例子:某库厂商开发一个算法,能在常时间内进行子串的查找工作。特点是其查找时间与目的串的长度无关。

该厂商创建了一个字符串类。为此,他生成了一个头文件和一个实现文件:

//faststring.hclass FastString{

char * m_psz; // 保存原始的字符串public:

FastString(const char*psz); // 构造函数~FastString(void);int Length(void)const; // 返回该字符串的长度int Find(const char*psz)const; // 查找指定的子串

} ;

Page 4: 三.  从 C++ 到 COM

4

//faststring.cpp#include "faststring.h"#include<string.h>

FastString::FastString(const char* psz):m_psz(new char[strlen(psz)+1]){strcpy(m_psz,psz);} // 分配内存

FastString::~FastString(void){ delete []m_psz;} // 释放内存

int FastString::Length(void) const{return strlen(m_psz);} // 计算长度

int FastString::Find(const char*psz)const{;} // 省略 , 这不是我们讨论的重点 .

Page 5: 三.  从 C++ 到 COM

5

客户使用方法 在客户应用中包含 faststring.h faststring.cpp #include "faststring.h"void f(){

FastString *pFS=new FastString;......

}缺陷:模块化特征消失殆尽1. 无法更新,升级,除非代码更换2. 代码更新后必须重新编译3. 所有的技术公开。

Page 6: 三.  从 C++ 到 COM

6

静态链接– 许多类库的做法– 编译时刻的链接

静态链接的缺点– 代码重复:多个程序各有自己的代码,需要更多的内存– 客户程序占据更多的外存空间– 库代码更新需要重新编译所有的客户程序

动态链接– 运行时刻的链接

动态链接形式– 编译时刻通过引入库– 运行时刻完全动态

2 动态链接库

Page 7: 三.  从 C++ 到 COM

7

使用动态链接库的方式可以解决上述问题 把 FastString 的所有方法从 DLL 中引出去

//faststring.hClass __declspec(dllexport) FastString //

__declspec(dllexport) 是 C++ 编译器指示符 , 它通知编译器 , 此类的所有成员方法都将对外输出 . 以下代码见FastStringDLL

{ char * m_psz;public:

FastString(const char*psz);~FastString(void);int Length(void)const;int Find(const char*psz)const;

} FastString 的所有方法都将被加载到 dll 的引出表中。允许在运行

时把每个方法的名字解析到内存中对应的地址。 链接器产生一个引入库 ( import library ),这个库暴露了

FastString 成员的符号,并没有包含实际的代码,它只是简单地包含一些引用,这些引用指向 dll 的文件名和被引出的符号的名字。当客户链接引入库时,有一些存根会被加入到可执行文件中,它在运行时通知装载器装载 dll 并且把所有被引入的符号解析到内存中相应的位置上。

Page 8: 三.  从 C++ 到 COM

8

客户的使用方法( VC 环境下): 静态加载 1.#include FastStringDLL.h 2. Setting -> link-> 加入 FastStringDll.Lib(VC6) 项目-》属性-》配置属性-》链接器-》输入-》附加依赖项 加入 FastStringDll.Lib ( VS.NET )

3 。 FastStringDll 。 Dll 放在可访问的路径中,特别地,放在一起。 4 。代码: FastString *pFS=new FastString("asdfg");

int i=pFS->Length(); 动态加载 对于输出类的情形不能使用动态加载。输出函数可以使用动态加

载。 名字改编问题: 使用了 extern”c” 保护的全局函数( vc 环境下)对生成的引入库

使用 coff2omf 工具转换后 bcb 可以顺利链接。 但是,对于未使用 extern ‘C’ 保护的函数不能顺利链接,即使使用动态加载也不行, GetProcAddress 函数将失败。类的成员函数不能使用extern ‘C’ 对其提供保护。因此上述代码在 bcb 下无法顺利链接 . (如前章所述 ,使用 DEF文件对引出的函数添加别名的方式 ,可以勉强解决这个问题 ,但是十分繁琐 ,不具备通用性 )

Page 9: 三.  从 C++ 到 COM

9

优点:1 。有效的技术保密2 。减少客户代码尺寸、节省资源3 。在特定的条件下(头文件保持不变时),客户可以顺利升级(无

需重新编译,直接更新 DLL )。 缺点:1 。名字改编问题,限定了编译器的使用。2 。在一般情况下,升级困难。见下节。

Page 10: 三.  从 C++ 到 COM

10

DLL 的升级 一个经常发生的情形是,库厂商要对库函数、类的实现进行升级。假设现在它们找到了一个更好的算法。

FastStringDLL2.0版。//FastStringDLL.h v 2.0

class FASTSTRINGDLL_API FastString{

const int m_cch;//v2.0 为了支持这个新的算法 , 必须增加一个成员变量 , 以存储字符串的长度 . 在构造函数中被赋值 , 以后 , 当客户查询长度时 ,直接返回 , 无需计算 . 导致了头文件的更新 .

char * m_psz;

public:

FastString(const char*psz);

~FastString(void);

int Length(void)const;

int Find(const char*psz)const;

}

Page 11: 三.  从 C++ 到 COM

11

// FastStringDLL.cpp v2.0

FastString::FastString(const char* psz)

:m_cch(strlen(psz)),/* 长度保存下来 */ m_psz(new char[m_cch+1])

{strcpy(m_psz,psz);}

FastString::~FastString(void)

{ delete []m_psz;}

int FastString::Length(void) const{

return m_cch;}// 直接返回 , 无需计算 , 提高了效率 .

int FastString::Find(const char*psz)const

{//; 省略return 0;

}

Page 12: 三.  从 C++ 到 COM

12

2.0版编译、发布。 新的客户使用新的头文件和新的 DLL 。 老的客户使用老的头文件和老的 DLL 。 在新老 DLL共存的情形下,由于类的定义发生了改变,这些应用系统很有可能产生不兼容。配置混乱、维护艰难。

版本问题的根源在于 C ++的编译模型,这种模型不能支持独立的二进制组件的设计。客户必须准确地知道对象的布局结构,从而导致客户和库的强的耦合关系。客户与库的紧密耦合性使得在不重新编译客户的情况下,无法实现类的替换。

升级不是无缝的。

Page 13: 三.  从 C++ 到 COM

13

3 接口类与实现类的分离

方法思路: 构造一个模型,把两个抽象的概念(即接口和实现)做成两个分

离的实体,即 C ++类,定义一个 C ++类使得它代表指向一个数据类型的接口;定义另一个 C ++类作为数据类型的实际实现,对象的实现者可以修改类的细节,而接口类保持不变。使用接口类屏蔽实现类,不会向用户暴露任何实现细节。

Page 14: 三.  从 C++ 到 COM

14

//faststringitf.hClass __declspec(dllexport) FasStringItf // 输出的是接口类而不是真正的实现类 . 接口类是实现类的一个封装 . 接口类的定义更容易保持不变性 .

{ class FastString; // 声明实现类 , 但是不需要包含实现类的头文

件 FastString* m_pThis; // 实现类的指针 .Public: FastStringItf(const char*psz); ~FastStringItf(void); int Length(void)const; int Find(const char*psz) const;};此接口类的二进制布局不会随着实现类的数据成员或方法成员的增删而改变,从而有效地将实现部分隐藏起来,客户的编译器不需要知道实现类的细节,(不需要包含实现类的头文件,因此实现者不必公布) 接口类的方法成为了 Dll 的入口点,接口方法的实现只是把方法的调用传递给实现类 ,如下 :

Page 15: 三.  从 C++ 到 COM

15

//faststringitf.cpp #include “faststringitf.h”#include “faststring.h”

FastStringItf::FastStringItf(const char *psz):m_pThis(new FastString(psz))

assert(m_pThis!=0);}

FastStringItf::~FastStringItf(void){ delete m_pThis;} int FastStringItf::Length(void) const{ return m_pThis->Length();}

int FastStringItf::Find(const char*psz)const{ return m_pThis->Find(psz);}

客户的使用方法与前相仿 , 创建一个接口类对象 ,而非实现类对象 ,如下 :

Page 16: 三.  从 C++ 到 COM

16

客户的使用方法 1. #include “faststringitf.h” // 包含接口类头文件 ,而非实现类的 . 2. Setting -> link-> 加入 FastStringDll.Lib(VC6) 项目-》属性-》配置属性-》链接器-》输入-》附加依赖项 加入 FastStringDll.Lib ( VS.NET )

3 。 FastStringDll 。 Dll 放在可访问的路径中,特别地,放在一起。 4 。代码: FastStringItf *pFSF=new FastStringItf("asdfg");

//显示地创建接口类 ,而非实现类 , 当然 , 内部创建了实现类

int i=pFSF->Length(); // 通过接口类调用实现类

实现者可以灵活改变其实现方法,而不会影响到现有的客户。 以上方法 ,较好地解决了版本升级的问题 , 不仅如此 ,最重要的

是提供了一种思路 . 虽然还很不完善 . 缺陷:1. 对于大的类库,成百上千的函数,都需要机械地进行调用的转移,冗长。

2. 每个方法增加两次调用,对于关键的应用性能上不理想。3. 链接器的兼容性仍然没有解决。

Page 17: 三.  从 C++ 到 COM

17

4.1 实现方式

前节中 , 接口类通过保留一个实现类的指针的方式来调用实现类的方法 .虽然取得重大进展 ,但显得笨拙 . C++ 允许我们使用更好的方式 :虚函数 .

假定在给定的平台上所有的编译器都使用同样的方法实现虚函数的调用机制,我们可以这样来定义 C ++的接口类:所有的公共操作都定义为虚函数,以保证所有的编译器为客户端的方法调用产生等价的机器码。

创建步骤: 1 。 Vc 下新建一个 dll 工程,头文件中声明接口类,并以 Extern’C’ 的

方式引出一个创建接口类的函数。//IFastString.hclass IFastString{ public:

virtual int Length(void) const=0;virtual int Find(const char*psz)const=0;};

extern "C" { __declspec(dllexport) IFastString *CreateFastString(const char*psz);}

4 抽象基类作为二进制接口

Page 18: 三.  从 C++ 到 COM

18

2 。新建一个实现类 FastString ,派生自接口类 IFastString, 类的声明以及实现分别在文件 FastString.h 和 FastString.cpp 中

class FastString : public IFastString {

const int m_cch;char*m_psz;

public:FastString(const char* psz); ~FastString();int Length(void)const; // 不需要加上 virtual 关键字int Find(const char*psz)const;

}; 实现类的定义与前面的一致,只是派生自接口类,因此它的两个函

数是虚函数。

Page 19: 三.  从 C++ 到 COM

19

实现代码如下:IFastString * CreateFastString(const char * psz){return new FastString(psz);} // 创建一个实现类的实例 . 返回值确实接

口指针 .

FastString::FastString(const char* psz):m_cch(strlen(psz)), m_psz(new char[m_cch+1]){strcpy(m_psz,psz);}

FastString::~FastString(void){ delete []m_psz;} // 释放成员变量占用的内存 .

int FastString::Length(void) const{return m_cch;}

int FastString::Find(const char*psz)const{// 具体方法省略}

// 实现方法与前面完全一致 , 所不同的是成员函数的性质变了 .

Page 20: 三.  从 C++ 到 COM

20

Dll 的四个文件:接口类的声明和实现,实现类的声明和实现,只有接口类的声明需要提供给客户。 Dll 只引出一个函数CreateFastString 。

客户的使用方法(在 C ++ Builder 下) 新建一个工程,加入接口类的头文件,加入 FastString.lib 引入文件。 #include “IFastString.h” // 客户需要接口类的声明文件 .

IFastString *pIFS=CreateFastString(“abcd");

// 创建一个实现类的实例 , 用一个接口类的指针指向它 .

pIFS->Find(“a”); // 通过接口类来调用实现类的功能 .

delete pIFS;

可以圆满解决链接器的兼容性问题。

Page 21: 三.  从 C++ 到 COM

21

4.2 内存泄漏问题

以上代码会导致内存泄漏,原因在于接口类的析构函数不是虚函数 . delete pIFS; 将导致接口类 ( 基类 ) 的析构函数被调用 .而实现类(派生类 ) 中所动态分配的保存字符串的内存将不能得到及时释放 .

简单的解决方法 : 把基类的析构函数声明为虚的 . 这样 ,派生类的析构函数也是虚的 . 执行 delete pIFS; 时 ,由于 pIFS 指向的是派生类的对象 , 所以调用的是派生类的析构函数 . 而删除派生类的对象时 , 析构函数的执行顺序为 :

1. 派生类本身的析构函数2. 对象成员的析构函数3. 基类的析构函数 因此派生类析构函数将递归地自动激活基类的析构函数 . 通过这样

的方法可以完整地释放派生类对象所占用的存储空间 . 但是如果将接口类的析构函数做成虚的,会破坏接口类的编译器独立性。因为虚析构函数在虚表中的位置随着编译器的不同而不同。

解决方法:给接口类增加一个 Delete 方法,作为其另一个纯虚函数,在派生类中实现自身的删除,以导致正确的析构过程。

Page 22: 三.  从 C++ 到 COM

22

于是 , 接口类的声明变为class IFastString{public:

virtual void Delete(void)=0;virtual int Length(void) const=0;virtual int Find(const char*psz)const=0;

};实现类的声明class FastString : public IFastString {

int m_cch;char*m_psz;

public:FastString(const char* psz); ~FastString();void Delete(void);int Length(void)const;int Find(const char*psz)const;

};

Page 23: 三.  从 C++ 到 COM

23

IFastString * CreateFastString(const char * psz){return new FastString(psz);} // 创建一个实现类的实例 . 返回值确

实接口指针 .void FastString::Delete(){ delete this; } //由用户调用 删除自身 .

FastString::~FastString(void){ delete []m_psz;} // 由上一个函数引起 释放成员变量占用的内

存 . // 构造函数 , Length Find 等不变 , 略 .

使用方法 ( 静态加载 ) : IFastString *pIFS=CreateFastString(“abcd");

pIFS->Find(“a”);

pIFS - >Delete() ; //调用的是实现类的函数实现 .

Page 24: 三.  从 C++ 到 COM

24

使用方法 ( 动态加载 )

IFastString *pIF;

HANDLE h;

h=LoadLibrary("FastString.dll");

if(h==NULL){ShowMessage("load err");return;}

typedef IFastString *(__stdcall *PF)(void);

PF pf;

pf=(PF)GetProcAddress(h,“CreateFastString");

pIF= pf(“abcd”); // 或 pIF=(*pf)(“abcd”);

pIF ->Length(); //…… 使用接口pIF->Find(“a”);

pIF->Delete();

FreeLibrary(h);

Page 25: 三.  从 C++ 到 COM

25接口类与实现类的内存结构

4.3 接口与实现类的内存结构

vptr Delete(null)Length(null)Find(null)

vptr FastString::DeleteFastString:: LengthFastString:: Find

IFastString

FastString

m_cchm_psz

this

this

Page 26: 三.  从 C++ 到 COM

26

通过这种方式我们可以安全地在 C ++环境中暴露 DLL 中的类,而在另一个 C ++环境中访问它。接口类作为客户与实现类的屏蔽 . 这种方式是 COM 中建立与编译器厂商无关的可重用组件的技术基础。

Page 27: 三.  从 C++ 到 COM

27

5 对象的扩展性

5.1 功能扩展的需求

至此客户可以动态地加载二进制组件,在接口类定义不变的情况下 , 实现者可以自由地对实现方案进行升级,客户无需重新编译。至此已经解决了前面所面临的链接器兼容性问题和类库的更新问题。

但是对象的接口却不能变化,因为客户在编译过程中需要接口的精确定义,对接口的任何变化都会导致客户的重新编译。而且对接口的任何改编都会破坏对象的封装性要求。

但是 , 对功能的需求却是无限的 ,如果擅自更改已经发布的接口类的话……

Page 28: 三.  从 C++ 到 COM

28

初始 IFastString 接口:class IFastString{public:

virtual void Delete(void)=0;virtual int Length(void) const=0;virtual int Find(const char*psz)const=0; };

更改后的接口:class IFastString{public:

virtual void Delete(void)=0;virtual int Length(void) const=0;virtual int Find(const char*psz)const=0;

virtual int FindN(const char*psz,int n)=0; // 新增加的虚函数。};老客户得到了包含 FindN 表项的新对象时,仍然能正常工作,然而,

新客户如果碰巧使用了老对象,当客户针对基于老接口定义编译得到的对象调用 FindN 时,程序崩溃了。

因此,接口定义一旦公开,是不能更改的。 功能扩展性解决方案:允许实现类暴露多个接口。这有两种途径: 1 。设计一个接口使得它继承自另一个相关的接口。 2 。让实现类继承多个不相关的接口。

Page 29: 三.  从 C++ 到 COM

29

对原接口进行扩展 , 生成新的接口 .比如使用从原接口继承的方式 ( 也可以使用别的方式 ,如嵌套类 )

class IFastString2:public IFastString{ public: virtual int FindN(const char*psz,int n)=0;};

新的实现类的声明 : (派生自新接口 )class FastString : public IFastString2{

int m_cch;char*m_psz;

public:FastString(const char* psz); ~FastString();void Delete(void); // IFastString 的方法int Length(void)const; int Find(const char*psz)const;

int FindN(const char*psz,int n); // IFastString2 的方法};而客户在使用时 :

5.2 方法一:扩展接口

Page 30: 三.  从 C++ 到 COM

30

客户在运行时询问对象,以确认对象是否支持新的接口。这里将使用dynamic_cast 运算符。

int Find10thBob(IFastString *pfs){ IFastString2 *pfs2=dynamic_cast<IFastString2 *>(pfs); if(pfs2) return pfs2->FindN("Bob",10); //如果实现了 IFastString2 接口 . 新实现类 (如果使用的是新的服务器 )

else error("can not find 10th occurrence of Bob"); //如果实现了 IFastString2 接口 . 老实现类 (如果使用的是老的服务器 )

}这种平滑的由用户决定的 downcast 能力,可以使得系统功能不断地

扩展、升级。

Page 31: 三.  从 C++ 到 COM

31

5.3 方法二:多接口 前一节新接口扩展的功能与原功能有一定的逻辑关系 . 当对象需要提供与原功能不相关的新功能时,比如说提供永久性支持,即在外部存储设备的存储能力。当然也可以仿照上面的办法创建一个新的接口派生自 IFastString :

class IPersistentObject:public IFastString{

Public:

Virtual bool Load(const char *pszFileName)=0;

Virtual bool save(const char *pszFileName)=0;

}

但是其他非 IFastString兼容的对象 ( 不需要支持 IFastString 接口的对象 ),也可能需要永久性支持,那么按照这种方案,对象支持IPersistentObject 接口 , 则这些对象也要支持 Length 、 Find等操作,而这些操作对它很可能并没有用处。

Page 32: 三.  从 C++ 到 COM

32

因此,为了尽可能使得 IPersistentObject 接口具有通用性,它应该是一个独立的接口,而不是派生自 IFastString :

Class IPersistentObject{

Public:

virtual void Delete()=0; // 使对象能够删除自身 virtual bool Load(const char *pszFileName)=0; // 实际功能 virtual bool Save(const char *pszFileName)=0;}

而实现类要同时支持此两个接口 :

Page 33: 三.  从 C++ 到 COM

33

实现类的声明 : (同时派生自两个类 )class FastString : public IFastString ,public IPersistentObject{

int m_cch;char*m_psz;

public:FastString(const char* psz); ~FastString();void Delete(void); // 一般的方法 ,虚表中有两项 ,同时改写int Length(void)const; // IFastString 的方法int Find(const char*psz)const;

bool Load(const char *pszFileName); // IPersistentObject 的方法 bool Save(const char *pszFileName);};

Page 34: 三.  从 C++ 到 COM

34

客户的使用方法:假如客户得到了 IFastString 指针,如果客户想要操作IPersistentObject提供的功能,只需要使用 RTTI得到一个指向对象暴露出来的 IPersistentObject 接口即可:

Bool SaveString ( IFastString*pfs,char *pszFN)

{ bool bResult=false;

IPersistentObject *ppo=dynamic_cast<IPersistentObject*>(pfs);

// 此处是 cross cast

If(ppo) {bResult=ppo->Save(pszFN); //如果是新对象 return bResult;}

else ….. //如果是老对象} 以上代码可以工作,因为编译器具有关于实现类的布局结构和类

型层次的足够多的信息以确定对象是否继承自IPersistentObject 。

Page 35: 三.  从 C++ 到 COM

35

5.4 中性的类型转换 每个编译器厂商对于 RTTI 的实现是不相同的,这破坏了以抽象

基类作为接口而获得的编译器的独立性。 我们可以中性地处理 dynamic - cast 的语义,不使用与编译器

相关的语言特征。从每个接口显示地暴露出一个广为人知的方法,来完成与 dynamic - cast语义等价的功能,而不强求各方使用同样的编译器。

这样两个接口的定义变为:

Page 36: 三.  从 C++ 到 COM

36

Class IPersistentObject{

Public:

virtual void * Dynamic_cast(const char*pszType)=0;

//注意 Dynamic_cast 是一个函数而不是操作符 virtual void Delete()=0; //删除操作 virtual bool Load(const char *pszFileName)=0; // 功能 virtual bool Save(const char *pszFileName)=0;} Class IFastString{

Public:

virtual void * Dynamic_cast(const char*pszType)=0;

virtual void Delete()=0;

virtual int Length()=0;

virtual int Find(const char*psz)=0;}

我们注意到两个接口都提供了 dynamic_cast 方法和 delete 方法。很自然地,我们把这两个方法提升到一个基类中 , 于是 :

Page 37: 三.  从 C++ 到 COM

37

Class IExtensibleObject{ // 实现通用的功能Public: virtual void * Dynamic_cast(const char*pszType)=0; // 转换 virtual void Delete()=0;} //删除

Class IPersistentObject : IExtensibleObject{Public: // 实际功能 virtual bool Load(const char *pszFileName)=0; virtual bool Save(const char *pszFileName)=0;}

Class IFastString : IExtensibleObject{Public: // 实际功能 virtual int Length()=0; virtual int Find(const char*psz)=0;}

而实现类的定义如下 :

Page 38: 三.  从 C++ 到 COM

38

class FastString : public IFastString ,public IPersistentObject{

int m_cch;char*m_psz;

public:FastString(const char* psz); ~FastString();

void * Dynamic_cast(const char*pszType) // 一般的方法 void Delete(void);

int Length(void)const; //IFastString 的方法int Find(const char*psz)const;

bool Load(const char *pszFileName); // IPersistentObject 的方法 bool Save(const char *pszFileName);};

Page 39: 三.  从 C++ 到 COM

39

FastString 的 Dynamic_Cast 方法的实现通过操纵对象的类层次结构,模拟出 RTTI 的功能。因为实现类是从接口类派生出来,所以可以使用显示的静态类型转换,把 this 指针转换到客户所请求的类型。

Void *FastString::Dynamic_Cast(const char *pszType){

If(strcmp(pszType,”IFastString”)==0)

return static_cast<IFastString*>(this);

else If(strcmp(pszType,”IPersistentObject”)==0)

return static_cast<IPersistentObject*>(this);

else If(strcmp(pszType,”IExtensibleObject”)==0)

return static_cast<IFastString*>(this);

Return 0;} //未支持接口的请求。 static_cast仅仅是在对象与子对象之间进行偏移的加减 . 是编译

器中性的 . dynamic_cast 要改变虚表指针所指向虚表的可见部分 ,其实现方式是编译器相关的 .

Page 40: 三.  从 C++ 到 COM

40

FastString 的类型层次结构

IExtensibleObject

IFastString IPersistentObject

IExtensibleObject

FastString

Page 41: 三.  从 C++ 到 COM

41

FastString 对象的二进制布局结构

vptr

vptr

M_cch

M_psz

FastString::Dynamic_cast

FastString::DeleteFastString::Length

FastString::Find

FastString::Dynamic_cast

FastString::DeleteFastString::LoadFastString::Save

this

从 IFastString继承来的虚

从 IFastString继承来的虚

Page 42: 三.  从 C++ 到 COM

42

6 资源管理

考虑以下客户代码: void f(void){

IFastString *pfs=0;

IPersistentObject *ppo=0;

pfs=CreateFastString("asdf");

if(pfs) {

ppo=(IPersistentObject*)

pfs->Dynamic_Cast("IPersistentObject");

if(!ppo)

pfs->Delete();//转换不成功,使用 pfs来删除对象。 else{

ppo->Save("c:\\myfile");

ppo->Delete();} //转换成功,使用 ppo来删除对象。 }

}

Page 43: 三.  从 C++ 到 COM

43

以上代码的意图是通过 IFastString 接口创建一个字符串,然后通过永久接口存储在文件中。

实体对象最初是通过 IFastString 接口与客户联系起来的,然而最后是通过 IPersistentObject 接口 delete 的,删除操作会通过虚表找到正确的函数,功能上不会出错。然而客户 ( 程序员 ) 必须 (在心里 )记录下哪个指针与哪个对象联系在一起,每个对象只能调用一次 Delete 方法,而且一旦调用后,所有与之关联的接口指针都不再有效。程序员必须对这些指针的生命周期完全把握清楚。这对于以上简单的例子并不难,但是在实际的开发工作中,采用以上的方式给开发人员带来较大的负担。

Page 44: 三.  从 C++ 到 COM

44

解决方案: 每个对象维护一个引用计数,当接口指针被复制的时候,计数增加;接口指针被销毁的时候,计数减少。

Class IExtensibleObject{

Public:

virtual void * Dynamic_cast(const char*pszType)=0;

virtual void Delete()=0;} 改进为: Class IExtensibleObject{

Public:

virtual void * Dynamic_cast(const char*pszType)=0;

virtual void DuplicatePointer(void)=0;

virtual void DestroyPointer(void)=0;

}

Page 45: 三.  从 C++ 到 COM

45

class FastString : public IFastString ,public IPersistentObject{ int m_cPtrs;// 引用指针数目

......public:

FastString(const char* psz):m_cPtrs(0){}; // 引用指针数目初始化为 0;

void DuplicatePointer(void) { ++m_cPtrs;} //增加引用计数 void DestroyPointer(void) { if(--m_cPtrs==0) delete this;} //减少引用计数 ,减到 0 时 ,删除对象自身 . ......}; CreateFastString 函数改为:IFastString *CreateFastString(const char*psz){ IFastString *pFS=new FastString(psz); If(pFS) pFS->DuplicatePointer(); return new pFS;}

Dynamic_Cast 函数改为:

Page 46: 三.  从 C++ 到 COM

46

Void *FastString::Dynamic_Cast(const char *pszType){

Void* pv=0;

If(strcmp(pszType,”IFastString”)==0)

pv=static_cast<IFastString*>(this);

else If(strcmp(pszType,”IPersistentObject”)==0)

pv= static_cast<IPersistentObject*>(this);

Else If(strcmp(pszType,”IExtensibleObject”)==0)

pv= static_cast<IFastString*>(this);

Return 0;//未支持接口的请求。Pv->DuplicatePointer(); // 引用计数加 1

Return pv;

}

Page 47: 三.  从 C++ 到 COM

47

于是:客户遵守以下两条: 1 。当接口指针被复制时,调用 DuplicatePointer 2 。当接口指针不再有用时调用 DestroyPointer

void f(void){ IFastString *pfs=0; IPersistentObject *ppo=0; pfs=CreateFastString("asdf"); if(pfs) { ppo=(IPersistentObject*) pfs->Dynamic_Cast("IPersistentObject"); if(ppo) { ppo->Save("c:\\myfile"); ppo->DestroyPointer();} pfs->DestroyPointer();//每个指针各自负责自己的引用计数操作。 }} 每个指针都被看作有独立生命周期的实体,客户无需把指针与对

象联系起来 ,直观 ,易用。客户只需要遵守两条规则,让对象自己管理其生命周期。

Page 48: 三.  从 C++ 到 COM

48

以上我们对一个可重用的二进制组件对象的改进过程,实际上就是 Microsoft 公司在 1988年到 1993年间设计 COM 的过程。