【必修】软件复用与组件技术 学习笔记(二) chapter2 COM、接口

    科技2025-11-05  10

    (一)COM

    COM规范就是一套为 组件构架设置 标准的文档。 【可以看做是组件的连接方式,组件可以被重写,被按照需求进行实现,但是构架设置不会轻易改变。】

    1.COM的组成:

    COM组件是动态链接库(DLLs)或者可执行文件(EXEs)的形式发布的可执行代码组成的。

    遵循COM规范编写的组件能够满足对组件架构的所有需求。

    COM由接口和实现两部分组成。 1) 实现部分对于客户来说是黑盒,客户能够看到的是组件的接口部分;

    2) 接口部分则由若干接口组成。

    a) 每个接口是有序排列的一组函数指针;可以简单地理解为一组函数; b) 每一个接口相当于该组件对外的一个窗口,客户可以通过不同的视角(接口)看到,获得该组件提供的服务; c) 对客户来说,组件就是接口集,只能通过接口与组件交互。

    2.COM的作用:

    COM是动态链接的(在运行时使组件之间发生关系)。COM使用DLL将组件动态链接起来。

    3.COM的要求:

    COM组件必须进行封装,它们可以被封装因为COM满足如下限制条件: 1) 与语言无关; 2) 可以以二进制形式发布; 3) 可以在不妨碍老顾客的情况下被升级; 4) 可以透明地在网络上被重新分配位置。对远程机器上的组件同本地机器上的组件的处理方式没有差别。

    4.COM的作用:

    1) 给其他应用程序提供面向对象的API或服务的好方法; 2) 建立快速构造应用程序,与语言无关的组件库的建立;

    5.COM库

    COM具有一个被称为是COM库的API,它提供的事对所有客户及组件都非常有用的组件管理服务。 COM库中的大多数代码均可以支持分布式或网络化的组件。

    6.COM方法

    我们可以把COM作为一种编写程序的方法。 比如,可以在任何操作系统上使用任何编程语言按COM风格进行编程。

    7.COM满足用户需要

    COM使用DLL来提供可在运行时被替换掉的组件,COM保证这些组件可以充分利用动态链接带来的好处,它使用如下方法来实现这一点: 1) 提供了一个所有组件都应该遵守的标准; 2) 允许使用组件的多个不同版本;(这一点对用户几乎完全透明) 3) 使得可以使用相同的方法来处理类似的组件; 4) 定义了一个与语言无关的架构; 5) 支持对远程组件的透明连接。

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

    8. COM的封装

    COM通过组件与客户之间的连接或接口来实现封装。(下一节为“接口”) ————————

    (二)接口

    1.接口的定义

    计算机程序实际上是通过一组函数而连接起来的,这组函数实际上就定义了程序中不同部分的接口。

    比较常见的接口,比如DLL接口,C++接口,DLL的接口就是它所输出的那些函数,C++类的接口则是该类的一个成员函数集(后面会提到)

    而COM的接口,涉及到一组由组件实现并提供给客户使用的函数,对于COM来说,接口是一个包含一个函数指针数组的内存结构。每一个数组元素包含的是一个由组建所实现的函数的地址。

    在C++中,可以使用抽象基类来实现COM接口。由于一个COM组建可以支持任意数目的接口,所以我们将用抽象基类的多重继承来实现它。

    2.接口的作用

    在COM中,接口就是一切。对于用户来说,一个组件就是一个接口集。

    3.接口实现了可复用的程序构架

    接口连接着被实现的模块,模块可以被任意实现,但接口相当于它们的组织方式,连接方式意味着积木最终会被搭建成什么样子。

    4.接口的其他优点

    接口使得用同样的方式来处理不同的组件,这种能力就被称作是多态。

    5.COM接口的实现

    #include <iostream> using namespace std; 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: virtual void Fx1() { cout<<"Fx1"<<endl;} //重写 virtual void Fx2() { cout<<"Fx2"<<endl;} virtual void Fy1() { cout<<"Fy1"<<endl;} virtual void Fy2() { cout<<"Fy2"<<endl;} }; int main() { CA c; c.Fx1(); c.Fx2(); c.Fy1(); c.Fy2(); }

    组件CA使用 IX 和 IY 实现了两个接口。

    其中,IX 和 IY是用于实现接口的纯抽象基类(仅包含纯抽象函数的基类)。

    (纯虚函数是指用 =0 标记的虚拟函数。)

    在定义纯抽象函数的类中,是不实现它们的。

    我们可以将抽象类看作是一个表单,派生类所做的就是填充此表单的空白。抽象基类制定了其派生类应该制定那些函数,而派生类则具体实现这些函数。 对纯虚函数的继承被称作是接口继承,这主要是因为派生类所继承的只是基类对函数的描述,抽象类并没有提供任何可供继承的实现细节。

    比如,在上面这个例子当中,IX 和 IY 就是纯抽象基类,它们的FX1和FX2函数就是没有实现细节的纯虚函数,但在 IX 和 IY 中列出了表单,就是可以在派生类,也就是(CA)当中的实现。

    最后,由于COM接口是与语言无关的,所以它对接口有一个二进制的标准要求,为了使一个纯抽象基类真正成为接口,它必须继承一个名为IUnknown的借口。

    6.编码约定

    为将接口同其他类的定义区分开来,作者使用了几种不同的编码约定——

    1)在接口名称的前面,加上一个大写字母“I”;

    2)在类的前面,加上一个大写字母“C”;

    3)此外,大多数情况下我们并不将一个接口定义成一个类,而是使用了Microsoft Win32 软件开发工具SDK中头文件中的定义:

    #define interface struct

    在这个定义之下,不需要另外在类中加入 public 关键字。因此,以上例子可以重新定义如下:

    #include <iostream> #include <objbase.h> using namespace std; class IX { // public: (去掉了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: virtual void Fx1() { cout<<"Fx1"<<endl;} //重写 virtual void Fx2() { cout<<"Fx2"<<endl;} virtual void Fy1() { cout<<"Fy1"<<endl;} virtual void Fy2() { cout<<"Fy2"<<endl;} }; int main() { CA c; c.Fx1(); c.Fx2(); c.Fy1(); c.Fy2(); }

    注意,在这个过程当中,IX 和 IY ,也就是纯抽象基类的Public被去掉了,但是实现类CA 中的public并没有被去掉。

    7.一个完整的例子(非动态连接)

    【把这个代码详细扒出来】 **①首先,引入#include <objbase.h>,**这个库是为了用 interface.

    interface: interface是面向对象编程语言中接口操作的关键字,功能是把所需成员组合起来,用来装封一定功能的集合。它好比一个模板,在其中定义了对象必须实现的成员,通过类或结构来实现它。接口不能直接实例化,即ICount ic=new iCount()是错的。接口不能包含成员的任何代码,只定义成员本身。接口成员的具体代码由实现接口的类提供。 接口使用interface关键字进行声明。

    ②main函数相当于用户——我个人的理解是“调用实现接口的类的地方才是客户”,不知道是否正确,待验证。

    ③在抽象接口之中有一个标记 __stdcall ,这个标记的作用是使用标准的调用决定,即这些函数将在返回到调用者之间将参数从栈中删除。 因为在常见的C或C++之间的调用约定中,栈的清理工作是由调用者完成的。

    #include <iostream> #include <objbase.h> //引入interface using namespace std; void trace(const char * pMsg) { cout <<pMsg <<endl; } //抽象接口 interface IX { virtual void __stdcall Fx1()=0; virtual void __stdcall Fx2()=0; } ; interface IY { virtual void __stdcall Fy1()=0; virtual void __stdcall Fy2()=0; }; class CA:public IX,public IY { public: virtual void __stdcall Fx1() {cout <<"CA::Fx1" <<endl; } virtual void __stdcall Fx2() {cout <<"CA::Fx2" <<endl; } virtual void __stdcall Fy1() {cout <<"CA::Fy1" <<endl; } virtual void __stdcall Fy2() {cout <<"CA::Fy2" <<endl; } }; //Client int main() { trace("Client:Create an instance of the component."); CA* pA = new CA; //Get an IX pointer IX* pIX = pA; trace("Client:Use the IX interface."); pIX->Fx1(); pIX->Fx2(); //Get an IY pointer IY* pIY =pA; trace("Client:Use the IY interface."); pIY->Fy1(); pIY->Fy2(); trace("Client:Delete the component."); delete pA; return 0; }

    总结

    1.COM接口在C++中是用纯抽象基类来完成的;

    2.一个COM组件可以提供多个接口。

    3.一个C++类可以使用多继承来实现一个可以提供多个接口的组件。

    Processed: 0.023, SQL: 8