component object model com 组件对象模型

Post on 17-Jan-2016

250 Views

Category:

Documents

19 Downloads

Preview:

Click to see full reader

DESCRIPTION

Component Object Model COM 组件对象模型. 概览. 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器. 应用程序. 一般由单个二进制文件组成 编译后至重新编译和发行生成新下一版本前,一般不会变化 操作系统、硬件和客户需求的改变必须等到整个应用程序被重新编译后才能认可 解决方案 将单个的应用程序分隔成多个独立部分-组件. 显而易见的优点. 随着技术的不断发展用新的组件取代已有的组件 程序随着新组件不断取代旧的组件而趋于完善 用已有的组件建立全新的应用. 组件应用程序. - PowerPoint PPT Presentation

TRANSCRIPT

Component Object Model Component Object Model

COMCOM

组件对象模型组件对象模型

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

应用程序应用程序• 一般由单个二进制文件组成• 编译后至重新编译和发行生成新下一版本

前,一般不会变化• 操作系统、硬件和客户需求的改变必须等

到整个应用程序被重新编译后才能认可

• 解决方案– 将单个的应用程序分隔成多个独立部分-组件

显而易见的优点显而易见的优点• 随着技术的不断发展用新的组件取代已有的组件• 程序随着新组件不断取代旧的组件而趋于完善• 用已有的组件建立全新的应用

组件 A 组件 B

组件 C 组件 D

组件 E

组件应用程序

组件 A 组件 B

组件 C 新改进后的组件 D

组件 E

改进后的组件应用程序

比较比较• 传统的做法

– 应用程序分割成文件、模块或类– 编译并链接成一个铁板一块般的应用程序

• 组件建立的应用程序– 微型应用程序:每个部分都已经编译、链接好

可以使用的– 可以在运行时同其它组件连接。– 需要修改和改进时,只需将某个构成组件用新

的版本替换

COMCOM

• Component Object Model

• 组件对象模型,是关于如何建立组件以及如何通过组件构建应用程序的一个规范。

• 开发 COM 的目标是为了使应用程序更易于定制、更为灵活。– OLE1 DDE– OLE2 COM

使用组件的优点使用组件的优点• 应用程序定制• 组件库• 分布式组件

对组件的需求对组件的需求• 组件的优点直接来源于可以动态地将它们

插入或卸出。• 所有组件必须满足两个条件:

– 组件必须动态连接• 目标是为在应用程序运行的过程中替换组件

– 组件必须封装其内部实现细节• 保持同样的连接方式

• 客户通过接口同其它组件进行连接

封装对组件的限制封装对组件的限制• 组件必须将其实现所用的编程语言封装起

来• 组件必须以二进制的形式发布• 组件必须可以在不妨碍已有用户的情况下

被升级• 组件在网络上的位置可以被透明地重新分

限制的考虑限制的考虑• 语言的无关性

– 内部: Object C , C++ , EspressoBeans– 市场: VB only ,对手任何语言编写组件

• 版本– 向后兼容的能力– 改变某个组件的功能以使之适应新应用程序的

需要、并同时支持老的应用程序

COMCOM 组件组件• COM 规范就是一套为组件架构设置标准的

文档• COM 组件是…

– COM 组件以动态链接库或可执行文件的形式发布的可执行代码组成。

– COM 组件满足下面的限制条件• COM 组件是完全与语言无关的• COM 组件可以以二进制的形式发布• COM 组件在不妨碍已有用户的情况下被升级• COM 组件在网络上的位置可以被透明地重新分布

COMCOM 组件组件• COM 组件不是…

– COM 不是计算机语言– COM 与 DLL相比或相提并论不合适– COM 不是一个 API 函数集合

• COM 库– COM具有一个被称为 COM 库的 API

• 提供组件管理服务• 保证对所有组件大多数重要的操作都可以按照相同

的方式完成• 节省开发和实现时间,支持分布式或网络化

COMCOM 综述综述• 提供了一个所有组件都应遵守的标准• 允许使用组件的多个不同版本,对用户几乎透明• 使得可以按相同的方式来处理类似的组件• 定义了一个与语言无关的框架• 支持对远程组件的透明链接

• COM强制开发人员必须将客户和组件严格地隔离开

接口的作用接口的作用• 在 COM 中接口就是一切• 对客户来说,一个组件就是一个接口集• 客户只能通过接口才能与 COM 组件交互

接口对接口对 COMCOM 程序的重要性程序的重要性• 可复用应用程序结构

A C

B D

E

接口A-C

接口B-D

接口 A-B

接口 B-E

接口 C-D

接口 D-E

接口的简单实现接口的简单实现class IX{public:

virtual void Fx1()=0;virtual void Fx2()=0;

};class IY{public:

virtual void Fy1()=0;virtual void Fy2()=0;

};class CA: public IX,public IY{

virtual void Fx1(){cout<<“Fx1”<<endl;}virtual void Fx2(){cout<<“Fx2”<<endl;}virtual void Fy1(){cout<<“Fy1”<<endl;}virtual void Fy2(){cout<<“Fy2”<<endl;}

};

说明说明• 对纯虚函数的继承被称为接口继承

–派生类所继承的只是基类对函数的描述–抽象类没有提供任何可供继承的实现细节

• 注意!– COM 是与语言无关的,对于接口,它有一个二

进制的标准–表示 COM 接口的内存块必须具有一定的结构– 必须继承 IUnknown 接口

约定约定# define interface struct

#include <objbase.h>interface IX{

virtual void _stdcall Fx1()=0;virtual void _stdcall Fx2()=0;

}interface IY{

virtual void _stdcall Fy1()=0;virtual void _stdcall Fy2()=0;

}

IXIX

IYIY

组件图形化表示

模拟实现模拟实现interface IX{…} ;interface IY{…} ;class CA:public IX,public IY{…};main{

CA *pA=new CA;IX* pIX=pA;pIX->Fx1();PIX->Fx2();

IY* pIY=pA;pIY->Fy1();pIY->Fy2();delete pA;return 0;

}

代码分析代码分析• 非接口通信

– 指向 CA 的指针– 使用了 new 和 delete控制组件的生命周期 C++

• 实现细节– 类并非组件– 接口并非总是继承的– 多重接口及多重继承 系统-组件-接口-函数– 命名冲突

• COM 接口是一个二进制标准• 客户同接口的连接不是通过其名称或其成员函数的名称完成的,而是

通过在内存块中的位置完成的。• 解决冲突的方法是不使用多重继承,可以合用指向实现的指针

接口理论接口理论• 接口的不变性

– 一旦公布了一个接口,那么它将永远保持不变– 升级时一般不修改已有接口,而是加入新接口

• 多态–按照同一种方式处理不同的对象

• 接口表示的行为越多,它的特定性越强,被其它组件复用的可能性越小

虚拟函数表虚拟函数表

vtbl指针vtbl指针&Fx2

&Fx3

&Fx4

&Fx1pIX

interface IX{

virtual void _stdcall Fx1()=0;

virtual void _stdcall Fx2()=0;

virtual void _stdcall Fx3()=0;

virtual void _stdcall Fx4()=0;

}虚拟函数表

IX

虚拟函数表与接口虚拟函数表与接口• 定义纯抽象基类就定义了相应的内存结构• 只有派生类实现抽象基类时才会分配

• COM 接口内存结构与 C++ 编译器为抽象基类生成的内存结构相同-巧合– IX即是一个接口– IX也是一个抽象基类

实例数据实例数据class CA: public IX{

virtual void _stdcall Fx1(){cout<<“Fx1”<<endl;}virtual void _stdcall Fx2(){cout<<m_Fx2<<endl;}virtual void _stdcall Fx3(){cout<< m_Fx3 <<endl;}virtual void _stdcall Fx4(){cout<< m_Fx4 <<endl;}

CA(double d):m_Fx2(d*d), m_Fx3(d*d*d) , m_Fx4(d*d*d*d) { }

double m_Fx2;double m_Fx3;double m_Fx4;

};

pApA

vtblvtbl 指针及实例数据指针及实例数据

vtbl指针

&Fx2

&Fx3

&Fx4

&Fx1

虚拟函数表

IX

&m_Fx2

&m_Fx3

&m_Fx4

Fx2

Fx3

Fx4

Fx1

客户

共享共享 vtblvtbl 实例数据实例数据

int main ()

{

CA* pA1=new CA(1.5);

CA* pA2=new CA(2.75);

}

pA1

pA2

pA1

pA2

多重实例多重实例vtbl指针

&Fx2

&Fx3

&Fx4

&Fx1

虚拟函数表&m_Fx2

&m_Fx3

&m_Fx4

Fx2

Fx3

Fx4

Fx1

客户

vtbl指针

&m_Fx2

&m_Fx3

&m_Fx4

不同类实例不同类实例class CB: public IX{

virtual void _stdcall Fx1(){cout<<“CB::Fx1”<<endl;}virtual void _stdcall Fx2(){cout<<“CB:: Fx2”<<endl;}virtual void _stdcall Fx3(){cout<< “CB:: Fx3”<<endl;}virtual void _stdcall Fx4(){cout<< “CB:: Fx4”<<endl;}

}

void foo(IX * pIX){pIX->Fx1();pIX->Fx2();

}

int main(){CA* pA=new CA(1.77);CB* pB=new CB;IX* pIX=pA;foo(pIX);

pIX=pB;foo(pIX);

};

pA1

pA2

pA1

pA2

多重实例多重实例vtbl指针

&Fx2

&Fx3

&Fx4

&Fx1

虚拟函数表

&m_Fx2

&m_Fx3

&m_Fx4

Fx2

Fx3

Fx4

Fx1客户

vtbl指针

&m_Fx2

&m_Fx3

&m_Fx4

&Fx2

&Fx3

&Fx4

&Fx1

虚拟函数表

Fx2

Fx3

Fx4

Fx1

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

接口查询接口查询• 客户同组件的交互都是通过接口完成的• 客户查询组件的其它接口时也要通过接口• IUnknown UNKNWN.Hinterface IUnknown{

virtual HRESULT _stdcall QueryInterface (const IID& iid, void** ppv)=0;

virtual ULONG _stdcall AddRef()=0;virtual ULONG _stdcall Release()=0;

};

IUnknownIUnknown

• 所有的 COM 接口都要继承 IUnknown• 所有 COM 接口都可以当作 IUnknown 接口处理• 组件的任何一个接口都可以用来获取它所支持的

其它接口。

pIXpIX vtbl指针vtbl指针

AddRef

Release

Fx

QueryInterface

虚拟函数表

IX

AddRef

Release

Fx

QueryInterface

客户 CA

获取获取 IUnknownIUnknown 指针指针• 自定义函数 (非标准 )

IUnknown* CreateInstance(){

IUnknown* pI=static_cast<IX*>(new CA);pI->AddRef();return pI;

}

– 标准方式HRESULT _stdcall CoCreateInstance(…);

QueryInterfaceQueryInterface

• 查询某个组件是否支持某个特定的接口virtual HRESULT _stdcall QueryInterface

(const IID& iid, void** ppv);

• IID :接口标识符{0x32bb8320,0xb41b,0x11cf,{0xa6,0xbb,0x0,0x80,0xc7,xb2,0xd6,0x82}}

• ppv :请求的接口指针地址• HRESULT : 32 位结构

– S_OK– E_NOINTERFACE– 宏 SUCCEEDED 和 FAILED处理结果

使用使用 QueryInterfaceQueryInterface

void foo(IUnknown pI){

IX* pIX=NULL;

HRESULT hr=pI->QueryInterface(IID_IX,(void**) &pIX);

if (SUCCEEDED(hr)){

pIX->Fx();}

}

实现类及其接口的继承关系实现类及其接口的继承关系

interface IX: IUnknown {/*…*/ };

interface IY: IUnknown {/*…*/ };

class CA: public IX, public IY {/*…*/ };

IXIX IYIY

IUnknownIUnknown IUnknownIUnknown

CACA

实现实现 QueriInterfaceQueriInterface

HRESULT _stdcall CA::QueryInterface(const IID& iid,void** ppv){

if (iid==IID_IUnknown){*ppv=static_cast<IX *>(this);

}else if (iid==IID_IX){*ppv=static_cast<IX*>(this);

} else if (iid==IID_IY){ *ppv=static_cast<IY*>(this);

}else{*ppv=NULL;return E_NOINTERFACE;

}static_cast<IUnknown*>(*ppv)->AddRef();reurn S_OK;

}

类型转换类型转换• 将 this转换成 IX得到的地址和转换成 IY得到的

地址是不同的static_cast<IX*>(this) != static_cast<IY*>(this)static_cast<void*>(this) != static_cast<IY*>(this)

• 旧式表达(IX*)this != (IY*)this(void*)this != (IY*)this

• 不使用如下转换*ppv= static_cast<IUnknown*>(this);

多重继承的内存结构多重继承的内存结构

IX vtbl指针

CA 实例数据 AddRef

Release

Fx

QueryInterface

AddRef

Release

Fy

QueryInterface

IY vtbl指针

虚拟函数表CA::this

(IX*)CA::this(IY*)CA::this

ΔIY

IX

IY

CA

void foo(IX* pIX);void bar(IY* pIY)int main(){

CA* pA=new CA;foo(pA);bar(pA);delete pA;return 0;

}

QueryInterfaceQueryInterface 的实现规则的实现规则• 1.QueryInterface返回的总是同一 IUnknown指针

• 2.若客户曾经获取过某个接口,那么它将总能获取此接口

• 3. 客户可以再次获取已经拥有的接口• 4. 客户可以返回到起始接口• 5.若能够从某个接口获取某特定接口,那么从任意接口都将可以获取此接口

同一同一 IUnknownIUnknown

• 组件的实例只有一个 IUnknown 接口• 为确定两个接口是否指向同一组件,可以通过这两个接口查询 IUnknown 接口,比较返回值

BOOL SameComponent(IX* pIX, IY* pIY){IUnknown* pI1=NULL;IUnknown* pI2=NULL;

pIX->QueryInterface(IID_IUnknown,(void**) &pI1);pIY->QueryInterface(IID_IUnknown,(void**) &pI2);

return pI1==pI2;}

可以获取曾经得到的接口可以获取曾经得到的接口• 如果 QueryInterface曾经成功过,那么同

一组件的后续 QueryInterface 将总是成功• 如果 QueryInterface失败,那么后续调用

将失败

• 保证接口集不会发生变化

可以再次获取已经拥有的接口可以再次获取已经拥有的接口• 拥有 IX 接口,查询 IX 接口• 所有接口都继承了 IUnknown ,许多函数需要一个 IUnknown指针

作为参数

void f(IUnknown* pI){IX* pIX=NULL;HRESULT hr=pIX->QueryInterface(IID_IX, (void**) &pIX);…

}void main(){

IX* pIX=GetIX();f(pIX);

}

可以从任何接口返回起始接口可以从任何接口返回起始接口void f(IX* pIX){

HRESULT hr;IX* pIX2=NULL;IY* pIY=NULL;hr=pIX->QueryInterface(IID_IY, (void**) &pIY);if (SUCCEEDED(hr)){

hr=pIX->QueryInterface(IID_IX, (void**) &pIX2);}…

}

若能够获取某特定接口,那么从任若能够获取某特定接口,那么从任意接口都将可以获取此接口意接口都将可以获取此接口

• 若可以通过接口 IX得到 IY , IY得到 IZ ,那么通过 IX也将得到 IZ

• 防止顺序依赖

• 以上规则的目标是为了是 QI 使用更简单、更富逻辑性、一致性以及确定性

QueryInterfaceQueryInterface 定义了组件定义了组件• 组件所支持的接口集就是 QueryInterface

能够为之返回接口指针的那些接口• 客户了解组件所支持接口的唯一方法是进

行查询• DCOM 中提供 IMultiQI

新版本组件的处理新版本组件的处理• COM 中接口不会变化,具有唯一接口标识符( II

D)• 可以建立新接口,并指定新的 IID• 无缝处理多个版本

– QI收到老 IID查询时返回老接口– 收到新 IID 时返回新接口

• 客户方面– 已有的客户运行不受影响– 新客户可以自行决定使用老的或新的接口

• 接口的标识同其版本是完全绑在一块的

新老版本组件组合新老版本组件组合

pIFlypIFly

pIFly

pIFlyFast

pIFly

pIFlyFast

IFlyIFly

IFlyFastIFlyFast

FastBronco

IFlyIFly

Bronco

何时建立新版本何时建立新版本• 接口中函数的数目• 接口中函数的顺序• 某个函数的参数• 某个函数参数的顺序• 某个函数产生的类型• 函数可能的返回值• 函数返回值的类型• 函数参数的含义• 接口中函数的含义

– 只要修改会导致已有客户的正常运行

版本约定版本约定• 新版本命名方式约定

– 在老版本后加一个数字 , IFly2

• 隐含合约– 仅保证函数名称及参数不变不足以保证修改组件不会

妨碍客户正常运行– 客户也许按照一定的方式或次序使用接口中函数– 所有的接口都有隐含合约– 避免隐含合约

• 使得接口不论成员函数如何被调用都能工作• 强制客户按一定的方式来使用此接口并在文档中声明这一点

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

生命周期控制生命周期控制• 客户不应直接控制组件的生命周期

– 使用完一个接口而仍要使用另一个接口时,不能释放组件

– 很难知道两个接口指针是否指向同一个对象– 程序越来越复杂时,决定何时释放组件是极为复杂的

• COM采用的方法– 通知组件何时需要使用它的某个接口以及何时使用完,

不是直接将接口删除– 组件的释放可以由组件在客户使用完其各个接口后自己完成

• 一般能够精确地知道何时开始使用一个接口• 大多数情况下可以精确地知道何时将用完此接口• 使用完接口后给组件发出指示比使用完整个接口更有意义

引用计数规则引用计数规则• COM 组件维护一个称作引用计数的数值• 基本规则

–返回接口指针的函数,在返回之前调用 AddRef 。

• 包括 QueryInterface 和 CreateInstance• 客户从这样的函数得到接口后,无须调用 AddRef

– 使用完接口之后调用 Release 。– 在赋值之后调用 AddRef 。

引用计数例引用计数例IUnknown* pIUnknown=CreateInstance();IX* pIX=NULL;HRESULT hr=

pIUnknown->QueryInterface(IID_IX, (void**)&pIX);

If (SUCCEEDED(hr)){

pIX->Fx();pIX->Release();

}

pIUnknown->Release();

IUnknown* pIUnknown=CreateInstance();IX* pIX=NULL;

HRESULT hr=pIUnknown->QueryInterface(IID_IX, (void**)&pIX);

pIUnknown->Release();

If (SUCCEEDED(hr)){

pIX->Fx();pIX->Release();

}

引用规则例引用规则例IUnknown* pIUnknown=CreateInstance();IX* pIX=NULL;HRESULT hr=

pIUnknown->QueryInterface(IID_IX, (void**)&pIX);pIUnknown->Release();If (SUCCEEDED(hr)){

pIX->Fx();IX* pIX2=pIX;pIX2->AddRef();pIX2->Fx();pIX2->Release();pIX->Release();

}

引用计数接口引用计数接口

• 只要能够使客户相信组件将记录每个接口本身维护引用计数值,实现方法无关紧要– 每个接口分别维护一个引用计数– 整个组件维护单个引用计数

IXIX

IYIY

组件

引用计数

IUnknownIUnknownIX

引用计数IX

引用计数

IY引用计数IY

引用计数

组件

IUnknown引用计数IUnknown

引用计数客户

• 客户要注意组件可能会为每个接口维护一个引用计数

IUnknown* pIUnknown=CreateInstance();IX* pIX=NULL;HRESULT hr=

pIUnknown->QueryInterface(IID_IX, (void**)&pIX);pIX->Fx();pIX* pIX2=pIX;pIUnknown->AddRef(); //Should be pIX->AddRef();pIX2->Fx();pIX2->Release();pIUnknown->Release(); //Should be pIX->Release();pIUnknown->Release();

每个接口一个引用计数每个接口一个引用计数• 原因

– 使程序调试更为方便– 支持资源的按需获取

• 在单独的组件中实现需要大量资源的接口

AddRefAddRef 和和 ReleaseRelease 实现实现ULONG _stdcall AddRef(){

return ++m_cRef; //InterlockedIncrement(&m_cRef);}

ULONG _stdcall Release(){if (--m_cRef==0){ //InterlockedDecrement(&m_cRef);

delete this;return 0;

}return m_cRef;

}返回值没有意义,客户不应将此值当成精确的引用计数

引用计数的优化引用计数的优化IUnknown* pIUnknown=CreateInstance();IX* pIX=NULL;HRESULT hr=

pIUnknown->QueryInterface(IID_IX, (void**)&pIX);pIUnknown->Release();

If (SUCCEEDED(hr)){pIX* pIX2=pIX;pIX2->AddRef();pIX->Fx();pIX2->Fx();pIX2->Release();pIX->Release();

}

生命周期嵌套生命周期嵌套组件 IUnknown pIX pIX2操作

CreateInstance

QueryInterface

pIUnknown->Release()

pIX2->AddRef()

pIX2=pIX

pIX2-

>Release(

)

pIX->Release()

一些情况一些情况• 函数中,无需对存在于局部变量的接口指针进行

引用计数– 包含在调用者的生命周期中void foo(pIX* pIX2){

pIX2->Fx();}

• 全面变量间进行指针赋值时,需要对该指针进行引用计数– 全局变量可以在任何地方释放

引用计数规则引用计数规则• 输出参数规则

– 任何在输出参数中或作为返回值返回一个新的接口指针的函数必须对此接口指针调用 AddRef

• 输入参数规则– 对传入函数的接口指针,无需调用 AddRef 和 Release

• 输入-输出参数规则– 对传递进来的接口指针必须给它赋另外一个接口指针前调用 Release– 在函数返回前,还必须对输出参数中保存的接口指针调用 AddRef

• 局部变量规则– 在函数的生命周期内,无需调用 AddRef 和 Release

• 全局变量规则– 在将其传递给另外一个函数之前,必须调用其 AddRef

• 不能确定时的规则– 都应调用 AddRef 和 Release

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

组件的创建组件的创建• 一个组件实际上不是 DLL

• 组件实际上应看成是 DLL 中所实现的接口集

• 客户在可以获取某个组件接口指针前,必须将 DLL 装载到进程空间中并创建此组件

从从 DLLDLL 中输出函数中输出函数extern “C”IUnknown* CreateInstance(){

IUnknown* pI=(IUnknown*)(void**)new CApI->AddRef();return pI;

}

模块定义文件模块定义文件CMPNT1.DEF

;

;Compnt1 module-definition file.

;

LIBRARY compnt1.dll

DESCRIPTION ‘(c) 2001-2005 cs’

EXPORTS

CreateInstance@1 PRIVATE

DLLDLL 装载装载typedef IUnknown* (*CREATEFUNCPTR)();IUnknown* CallCreateInstance(char* name){

HINSTANCE hComponent=::LoadLibrary(name);if (hComponent==NULL){

cout<<“CallCreatInstance:Cannot load.”<<endl;return NULL;}CREATEFUNCPTR CreateInstance

=(CREATEFUNCPTR)::GetProcAddress(hComponent, “CreateInstance”);

if (CreateInstance==NULL){cout<<“Can not find CreateInstance function.”<<endl;return NULL;

}return CreateInstance();

}

COM 库函数 CoCreateInstance

进程空间进程空间

DLL3 映象内存

进程 2

应用程序映象内存

DLL1 映象内存

DLL2 映象内存

进程 1

应用程序映象内存

DLL2 映象内存

DLL4 映象内存

未映象区域 未映象区域

4G

B

4G

B

客户和组件文件构成客户和组件文件构成

CLIENT1.CPP

CREATE.HCREATE.CPP

IFACE.HGUIDS.CPP

CMPNT1.CPP

CMPNT1.DEF

客户文件 共享文件 组件文件

GUID-Globally Unique IdentifierGUID-Globally Unique Identifier

• IID 实际是 128 比特( 16字节)的 GUID结构

• GUID最初由 OSF 定义的( UUID)

COMCOM 库函数库函数• HRESULT CoInitialize(void* reserved);

• void CoUninitialize();

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

CoCreateInstanceCoCreateInstance

HRESULT _stdcall CoCreateInstance(

const CLSID& clsid,

IUnknown* pIUnknownOuter,

DWORD dwClsContext,

const IID& iid,

void** ppv);

CoCreateInstanceCoCreateInstance 例例IX* pIX=NULL;HRESULT hr=::CoCreateInstance(CLSID_Compnt1,

NULL,CLSCTX_INPROC_SERVER,IID_IX,(void**)&pIX);

If (SUCCEEDED(hr)){pIX->Fx();pIX->Release();

}

类上下文类上下文• CLSCTX_INPROC_SERVER

• CLSCTX_INPROC_HANDLER

• CLSCTX_LOCAL_SERVER

• CLSCTX_REMOTE_SERVER

类厂类厂• 没有给客户提供一种能够控制组件创建过程的方

法• 在建立好一个组件之后,无法控制将组件装载到

内存中何处或检查客户是否有权限。

• 客户可以通过类厂组件创建其它组件• 类厂组件的唯一功能就是创建其它的组件• 创建组件的标准接口是 IClassFactory

– 用 CoCreateInstance创建的组件实际上是通过 IClassFactory创建的。

CoGetClassObjectCoGetClassObject

• 创建某个 CLSID相应的类厂

HRESULT _stdcall CoGetClassObject(const CLSID& clsid,DWORD dwClsContext,COSERVERINFO* pServerInfo, //for DCO

Mconst IID& iid,void** ppv

);

IClassFactoryIClassFactory

Interface IClassFactory: IUnknown{

HRESULT _stdcall CreateInstance(IUnknown* pUnknownOuter,const IID& iid,void** ppv);

HRESULT _stdcall LockServer(BOOL bLock);};

CreateInstanceCreateInstance 实现实现HRESULT _stdcall CFactory::CreateInstance(

IUnknown* pUnknownOuter,

const IID& iid,

void** ppv){

CA* pA=new CA;

if (pA==NULL){

return E_OUTOFMEMORY;

}

HRESULT hr=pA->QueryInterface(iid,ppv);

pA->Release();

return hr;

}

实现方式实现方式HRESULT CoCreateInstance(

const CLSID& clsid, IUnknown* pUnknownOuter,

DWORD dwClsContext, const IID& iid, void** ppv)

{*ppv=NULL;IClassFactory* pIFactory=NULL;HRESULT hr=CoGetClassObject(clsid, dwClsContext, NULL, IID_IClassFactory, (void**)&pIFactory);if (SUCCEEDED(hr)){

hr=pIFactory->CreateInstance(pUnknownOuter, idd, ppv);pIFactroy->Release();}return hr;

}

使用使用 CoGetClassObjectCoGetClassObject

• 使用不同于 IClassFactory 的某个接口来创建组件

• 创建同一个组件的多个实例• 客户对组件的创建过程进行更多的控制

类厂的特性类厂的特性• 类厂的一个实例将只能创建同某个 CLSID相应的组件

• 与某个特定的 CLSID相应的类厂将是由实现组件的开发人员实现的

• 类厂可以知道并确实具有它所创建的组件的一些特殊知识

DLLGetClassObjectDLLGetClassObject

STDAPI DLLGetClassObject(

const CLSID& clsid,

const IID& iid,

void** ppv);

创建类厂创建类厂STDAPI DLLGetClassObject( const CLSID& clsid,

const IID& iid,void** ppv)

{if (clsid!=CLSID_Compnt1){

return CLASS_E_CLASSNOTAVAILABLE;}CFactory* pFactory=new CFactory;if (pFactory==NULL){

return E_OUTOFMEMORY;}HRESULT hr=pFactory->QueryInterface(iid,ppv);pFactory->Release();return hr;

}

组件创建过程组件创建过程

IClassFactoryIClassFactory

IXIX

调用CoGetClassObject

客户

pIClassFactory

pIX

CoGetClassObjectCoGetClassObjectDLLGetClassObjectDLLGetClassObject

1COM 库 DLL

2

创建类厂 3返回 IClassFactory4

5调用IClassFactory::CreateInstance

创建组件

67返回 IX

8调用 IX::Fx

DLLMainDLLMain

BOOL APIENTRY DLLMain ( HANDLE hModule,

DWORD dwReason,

void** lpReserved)

{

if (dwReason==DLL_PROCESS_ATTACH){

g_hModule=hModule;

}

return TRUE;

}

一个一个 DLLDLL 中多个组件中多个组件

类厂 1类厂 1

组件 1组件 1

DLLGetClassObjectDLLGetClassObject调用CoCreateInstance调用CoCreateInstance

客户 DLL

类厂 n类厂 n

组件 n组件 n

类厂实现的复用类厂实现的复用

类厂类厂

组件 1

CreateFunction_1

组件 1

CreateFunction_1

DLLGetClassObjectDLLGetClassObject调用CoCreateInstance调用CoCreateInstance

客户 DLL

CLSID_1CLSID_1

组件 n

CreateFunction_n

组件 n

CreateFunction_n

&CreateFunction_1&CreateFunction_1

CLSID_2CLSID_2 &CreateFunction_2&CreateFunction_2

CLSID_2CLSID_2 &CreateFunction_2&CreateFunction_2

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

COMCOM 的继承特性的继承特性• COM 不支持实现继承• COM 支持接口继承

• 理由 :– 实现继承使得一个对象的实现同另外一个对象

的实现紧密地关联起来• 基类修改后 ,派生类无法正常运行而必须修改• 无法得到源代码

包容简介包容简介• 包容也是在接口级完成的

– 外部组件包含指向内部组件接口的指针– 外部组件将调用转发给内部组件 , 并可增加一些代码

IYIY

外部组件

IXIX

IZIZ

IYIY

外部组件

IXIX

IYIY

内部组件 内部组件

聚合简介聚合简介• 外部组件将直接把内部组件的接口指针返回给客户– 外部组件无需重新实现并转发接口中的所有函数

• 客户不应知道是在与两个不同的组件交互• 无法改造内部组件

IXIX

IYIY

外部组件

内部组件

包容的实现包容的实现class CA: public IX, public IY{

public://IUnknown interface …QI,AddRef,Releasevirtual void _stdcall Fx(){cout<<“Fx”<<endl;}virtual void _stdcall Fy() {m_pIY->Fy();}CA();~CA();HRESULT Init();

private:long m_cRef;IY* m_pIY;

};

包容的实现包容的实现CA::CA():m_cRef(1),m_pIY(NULL){

InterlockedIncrement(&g_cComponents);

}

CA:: ~CA(){InterlockedDecrement(&g_cComponents);if (m_pIY!=NULL){

m_pIY->Release();}

}

包容的实现包容的实现HRESULT CA::Init(){

HRESULT hr=::CoCreateInstance( CLSID_Compnt2, NULL,CLSCTX_INPROC_SERVER, IID_IY, (void**)&m_pIY);

if (FAILED(hr)){return E_FAIL;

}else{return S_OK;

}}

包容的实现包容的实现HRESULT _stdcall CFactory::CreateInstance( IUnknown* pUnknownOuter, const II

D& iid, void** ppv){if (pUnknownOuter!=NULL){

return CLASS_E_NOAGGREGATION;}CA* pA=new CA;if (pA==NULL){

return E_OUTOFMEMORY;}HRESULT hr=pA->Init();if (FAILED(hr)){

pA->Release();return hr;

}hr=pA->QueryInterface(iid,ppv);pA->Release();return hr;

}

接口扩展接口扩展interface IAirplane:IUnknown{

void Takeoff();void Fly();void Land();

}

Interface IFloatPlane:IAirplane{void LandingSurface(UINT iSurfaceType);void Float();void sink();

}

接口扩展接口扩展Void CMyFloatPlane::Fly(){

m_pIAirplane->Fly();}

void CMyFloatPlane::Land(){if (m_iLandingSurface==WATER){

WaterLanding();}else{

m_pIAirplane->Land();}

}

聚合的实现聚合的实现• 客户向外部组件请求接口 IY

• 外部组件不实现 IY 接口 , 向内部组件请求查询此 IY 接口

• 将查询到的 IY 接口指针返回给客户• 客户直接使用此指针调用内部组件所实现

的 IY 成员函数

聚合聚合 -- 类声明类声明

class CA: public IX{public :

//Add IUnknown interface declaration herevirtual void _stdcall Fx(){cout<<“Fx”<<endl;}CA();~CA();HRESULT Init();

private:long m_cRef;IUnknown* m_pInnerUnknown;

};

聚合聚合 --QueryInterfaceQueryInterface 实现实现

HRESULT _stdcall CA::QueryInterface(const IID& iid,void** ppv){if (iid==IID_IUnknown){

*ppv=static_cast<IX*>(this);} else if (iid==IID_IX){

*ppv=static_cast<IX*>(this);} else if (iid==IID_IY){

return m_pInnerUnknown->QueryInterface(iid,ppv);} else{

*ppv=NULL;return E_NOINTERFACE;

}reinterpret_cast<IUnknown *>(*ppv)->AddRef();return S_OK;

}

不正确的不正确的 IUnknownIUnknown

• 聚合的目标是使客户确信内部组件所实现的某个接口是由外部组件实现的

• 客户查询属于内部组件的接口时 , 所获得的组件功能视图与它查询外部组件的接口是不同的– 如果将内部组件按照通常方式实现的接口指针

传递给客户 , 客户将会得到分裂的视图• 违反“若能够从某个接口获取某特定接口,则从任意接口都能获取此接口”

组件的分裂视图组件的分裂视图

QueryInterfaceQueryInterface

IX

外部组件的IUnknown 实现外部组件的

IUnknown 实现AddRefAddRef

ReleaseRelease

FxFx

QueryInterfaceQueryInterfaceI

Y内部组件的

IUnknown 实现内部组件的

IUnknown 实现AddRefAddRef

ReleaseRelease

FyFy

外部组件

内部组件

解决方案解决方案• 问题的根源在于

– 内部组件实现了自己的 IUnknown 接口– 客户独立于聚合组件的实现,客户得到两个不同的 IUn

known 接口• 原则:不同的接口当且仅当它们返回相同的 IUnk

nown 接口指针时被认为是同一个组件。• 解决:应将内部组件的 IUnknown 接口向客户隐藏,而只给用户提供一个 IUnknown 接口– 内部组件上的接口必须使用外部组件所实现的 IUnkno

wn 接口– 外部组件的接口称作外部未知接口或者控制未知接口

外部未知接口外部未知接口HRESULT _stdcall CoCreateInstance(

const CLSID& clsid,IUnknown* pIUnknownOuter,DWORD dwClsContext,const IID& iid,void** ppv);

HRESULT _stdcall CreateInstance(IUnknown* pUnknownOuter,const IID& iid,void** ppv);

代理和非代理未知接口代理和非代理未知接口• 为支持聚合,内部组件实际将实现两个 IUnknown 接口• 非代理未知接口

– 将按照通常的方式实现内部组件的 IUnknown 接口• 代理未知接口

– 把 IUnknown 成员函数调用转发给外部未知接口或非代理未知接口

• 如果内部组件没有被聚合,代理未知接口将这些调用转发给非代理未知接口

• 如果内部组件被聚合了,代理未知接口将这些调用转发给由外部组件实现的外部未知接口

• 使用关系– 聚合组件的客户调用代理未知接口– 外部组件通过非代理未知接口来操作内部组件

未聚合组件未聚合组件

QueryInterfaceQueryInterface

IY 代理 IUnknown实现

代理 IUnknown实现

AddRefAddRef

ReleaseRelease

FyFy 非代理 IUnknown实现

非代理 IUnknown实现

聚合组件聚合组件

QueryInterfaceQueryInterface

IX

外部 IUnknown 实现外部 IUnknown 实现AddRefAddRef

ReleaseRelease

FxFx

非代理 IUnknown 实现非代理 IUnknown 实现

QueryInterfaceQueryInterface

IY

AddRefAddRef

ReleaseRelease

FyFy

代理 IUnknown 实现代理 IUnknown 实现

控制内部组件的代码

非代理未知接口声明非代理未知接口声明struct INondelegatingUnknown{

virtual HRESULT _stdcall NondelegatingQueryInterface(const IID&,

void**)=0;virtual ULONG _stdcall NondelegatingAddRef()=0;virtual ULONG _stdcall NondelegatingRelease()=0;

}

非代理未知接口实现非代理未知接口实现HRESULT _stdcallCB::NondelegatingQueryInterface(const IID& iid, void** ppv){

if (iid==IID_IUnknown){ *ppv=static_cast<INondelegatingUnknown*>(this);} else if (iid==IID_IY){

*ppv=static_cast<IY*>(this);} else {

*ppv=NULL;return E_NOINTERFACE;

}reinterpret_cast<IUnknown*>(*ppv)->AddRef();return S_OK;

}

代理未知接口实现代理未知接口实现class CB: public IY, public INondelegatingUnknown{

public:virtual HRESULT _stdcall QueryInterface(const IID& iid, void** ppv){

return m_pUnknownOuter->QueryInterface(iid,ppv);}virtual ULONG _stdcall AddRef(){

return m_pUnknownOuter->AddRef();}Virtual ULONG _stdcall Release(){

return m_pUnknownOuter->Release();}// Add NondelegatingUnknown interface declaration herevirtual void _stdcall Fy(){cout<<“Fy”<<endl;}CB(IUnknown* m_pUnknownOuter);~CB();

private:long m_cRef;IUnknown* m_pUnknownOuter;

};

外部组件的外部组件的 InitInit 函数函数HRESULT _stdcall CA::Init(){

IUnknown* pUnknownOuter=this;HRESULT hr=CoCreateInstance( CLSID_Compnt2,

pUnknownOuter, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pUnknownInner);

if(FAILED(hr)){return E_FAIL;

}hr= m_pUnknownInner->Queryinterface(IID_IY, (void**)&m_pIY);if(FAILED(hr)){

m_pUnknownInner-> Release();return E_FAIL;}pUnknownOuter->Release(); return S_OK;

}

内部组件的内部组件的 IClassFactory::CreateInstaIClassFactory::CreateInstancence

HRESULT _stdcallCFactory::CreateInstance( IUnknown* pUnknownOuter,

const IID& iid, void** ppv){

if ((pUnknownOuter!=NULL)&&(iid!=IID_IUnknown)){return CLASS_E_NOAGGREGATION;

}CB* pB=new CB(pUnknownOuter);if (pB==NULL){

return E_OUTOFMEMORY;}HRESULT hr=pB->NondelegatingQueryInterface(iid,ppv);pB->NodelegatingRelease();return hr;

}

内部组件构造函数内部组件构造函数CB::CB(IUnknown* pUnknownOuter):m_cRef(1){

::InterlockedIncrement(&g_cComponents);if (pUnknownOuter==NULL){

m_pUnknownOuter=reinterpret_cast<IUnknown*> (static_cast<INondelegatingUnknown*>(this));} else {

m_pUnknownOuter=pUnknownOuter;}

}

外部组件指向内部组件指针外部组件指向内部组件指针else if (iid==IID_IY){

return m_pUnknownInner->QueryInterface(iid,ppv);

}

或者else if (iid==IID_IY){

*ppv=m_pIY;

}

释放内部组件释放内部组件• 首先要保证组件不会试图再次将自己释放• 其次需要对外部组件调用 AddRef

– 对内部组件的 Release调用将会导致对外部组件的 Release调用。

• 最后释放外部组件

盲目聚合盲目聚合

else if (iid==IID_IX){

*ppv=static_cast<IX*>(this);

} else {

return m_pInner->QueryInterface(iid,ppv);

}

• 大多数情况不应该使用盲目聚合– 内外组件的接口可能不兼容

元接口元接口• 元接口:最不可能同组件中已有接口出现冲突的接口

• 用于操作组件本身

ISetColorsISetColorsIToolInfoIToolInfo

IMorphIMorph

内部组件

IColorsIColors

外部组件变换工具

变换算法

现实中的聚合和包含现实中的聚合和包含

• 为提供内部状态信息,内部组件可以加上新的接口,提供状态信息

IXIX

IYIY

内部组件

IZIZ

外部组件

外部组件的实现

IInternalStateIInternalState

接口实现接口实现

IXIXIX

实现IX

实现

IYIY

IZIZ

独立实现的组件

IY实现IY

实现

IZ实现IZ

实现

IXIX

IYIY

IZIZ

共享了某些内容的组件

IX,IY,IZ实现

IX,IY,IZ实现

虚拟函数模拟虚拟函数模拟• 基类可以在完成某个操作前、中、后调用某个虚拟函数给派生类提供一个定制操作的机会

IXIX

组件

客户调用 IX

客户

ICustomizeICustomize组件调用 ICustomize,给客户提供定制 IX行为的机会

ICustomizeICustomize

缺省定制组件客户的 ICustomize 实现可以包含或聚合缺省定制组件

概览概览 组件和接口 QueryInterface 函数 引用计数 动态链接 类厂 组件复用:包容与聚合 服务器

• 每个 EXE 文件都将在不同的进程中运行• 每个进程都有其自己的进程空间• DLL 将被映射到链接它们的 EXE 进程空间

不同的进程不同的进程

0x0000ABBA0x0000ABBApFoopFoo

pFoopFoo

0x0000ABBA0x0000ABBA

0x0000ABBA0x0000ABBA 0x0BAD0ADD0x0BAD0ADD

0x000012340x00001234

进程 1

进程 2

进程 1地址空间

进程 2地址空间

物理内存

进程内接口访问进程内接口访问• DLL 被称作是进程中服务器• EXE 被称作是进程外服务器(本地服务器)

• 接口实际上是一个函数指针数组• 为调用接口中的函数,客户必须能够访问同

接口相关的内存• 组件是 DLL ,与客户处于相同的地址空间

跨进程边界接口跨进程边界接口• 一个进程需要能够调用另外一个进程中的

函数• 一个进程需要能够将数据传递给另外一个

进程• 客户无需关心它所访问的服务器是进程内

服务器还是进程外服务器

本地过程调用(本地过程调用( LPCLPC ))• LPC 是基于 RPC 的用于单机上进程间通信

的 MS 专有技术• LPC 由操作系统实现

– 操作系统知道同每一个进程逻辑地址空间相对应的物理地址,操作系统可以调用任意进程中的任意函数

组件客户

EXE EXE进程边界

LPC

MarshalMarshal

• 将函数调用的参数从一个进程的地址空间中传到另外一个进程的地址空间中

• 若两个进程的都在同一台机器上,调整的过程相当直接:只需将参数数据从一个地址空间复制到另一个的地址空间

• 若在不同的机器上,考虑不同机器在数据表示方面的不同,必须将参数数据转换成标准格式– 实现 IMarshal 接口,在 COM创建组件的过程中查询– 在调用函数前后使用 IMarshal 成员函数调整或反调整

Proxy\Stub DLLProxy\Stub DLL

• 客户应该可以按照相同的方式与进程中、本地和远程组件进行通信。

• 代理:同另外一个组件行为相同的组件,DLL 形式,需要访问用户进程空间并进行数据调整

• 桩:对客户传来的数据进行反调整,对回传给客户的数据进行调整

COMCOM 使用的结构使用的结构

组件客户

EXE EXE

进程边界

LPC

代理对参数进行调整

桩对参数进行反调整

DLL DLL

MIDLMIDL 简介简介import “unknown.idl”;[

object,uuid(32bb8323-b41b-11cf-a6bb-0080c7b2d682),helpstring(“IX Interface”),pointer_default(unique)

]Interface IX:IUnknown{

HRESULT FxStringIn([in,string]wchar_t* szIn);HRESULT FxStringOut([out,string]wchar_t** szOut);HRESULT FyArrayIn( [in] long sizeIn,

[in size_is(sizeIn)] long arrayIn[])}

Pointer_defaultPointer_default

• ref -将指针当成是引用对待。此时表示此指针将总是指向一个合法的地址,并可反引用。调用前后指向同一地址,不能为空。 不能为它们指定别名。

• unique -此类指针可以为空,并且在函数内可以修改它们的值。

• prt -指定相应的指针就是一个 C指针,可以有别名、可以为空,其值可以被修改。

输入输出参数输入输出参数• in -仅仅需要将此参数从客户传递给组件• out -参数仅用来从组件向客户传回有关数据。

• 对于输出参数, MIDL 要求必须是一个指针。

HRESULT foo([in] int x, [int,out] int* y, [out] int* z);

IDLIDL 中的字符串中的字符串• 对数据进行调整时必须知道此块数据大小• 在函数中加上一个 string 修饰符, MIDL知道相应的参数为一个字符串,通过查找末尾空格决定其长度

• 标准约定: Unicode字符即 wchar_t 。

IDLIDL 中的结构中的结构

typedef struct{

double x;

double y;

double z;

} Point3d;

HRESULT FzStructOut( [out] Point3d* pt);

COM/DCOMCOM/DCOM 系统结构系统结构

DCOMDCOM 部件部件

top related