COM编程之二 接口
【1】接口
DLL的接口是它所输出的那些函数。
C++类的接口是该类的一个成员函数集。
COM接口是包含一个函数指针数组的内存结构。
每一个数组元素包含的是一个由组件所实现的函数的地址。
在COM中接口就是一切。对于客户而言,一个组件就是一个接口集。
客户只能通过接口才能同COM组件打交道。
对程序员而言,接口对于一个应用程序是最重要的,组件本身只不过是接口的实现细节。
一个接口是一个函数集合,一个组件则是一个接口集,而一个系统则是一系列组件的集合。
如下图更为精确地显示了接口对于COM程序的重要性:
【2】COM接口的优点
1、COM接口可以保护系统免受外界变化的影响。
2、COM接口使得客户可以用同样的方式来处理不同的组件。(这种能力即多态)
3、COM接口可以使内部具体实现得以隐藏。
【3】COM接口的C++实现
实现代码如下:
1 class IX //First interface
2 {
3 public:
4 virtual void Fx1() = 0;
5 virtual void Fx2() = 0;
6 };
7
8 class IY //Second interface
9 {
10 virtual void Fy1() = 0;
11 virtual void Fy2() = 0;
12 };
13
14 class CA : public IX, public IY //Component
15 {
16 public:
17 //Implemention of abstract base calss IX
18 virtual void Fx1() { cout << "Fx1" << endl; }
19 virtual void Fx2() { cout << "Fx2" << endl; }
20 //Implemention of abstract base class IY
21 virtual void Fy1() { cout << "Fy1" << endl; }
22 virtual void Fy2() { cout << "Fy2" << endl; }
23 };
可见,IX和IY是实现接口的纯抽象基类。
纯抽象基类指仅包含纯虚函数的基类。
纯虚函数在派生类中必需实现。否则 派生类仍不可实例化。
对纯虚函数的继承被称为接口继承。
CA是组件,IX、IY是接口,CA同时实现IX和IY两个接口。
一个COM组件可以有多个接口。由多重继承实现。
COM接口在C++中是使用纯抽象基类来实现的。
一个C++类可以使用多继承来实现一个可以提供多个接口的组件。
一个COM组件可以由多个C++类来完成,如可以再包含CFactory类。
一个COM组件也可以不包含任何C++类,如用C语言实现。
IX和IY并非真正意义的COM接口,真正的COM接口必须继承一个名为IUnknown的接口。
【4】识别interface关键字的本质
为了避免将接口定义成一个类的形式,微软在OBJBASE.H中定义如下:
#define interface struct
定义为struct的原因在于struct的成员将自动具有共有属性,因此无需因加public造成不必要的混乱。
【5】图形化表示接口与组件
图形化表示接口与组件。如下图所示:
【6】如何解决名称冲突?
当一个组件包含有多个接口时,可能会发生名称冲突。
一是接口名称之间的冲突,
二是一个接口内部的函数与另外一个接口内部的函数名称的冲突。
可以用下列方法来解决:
1、接口名称的冲突可以使用某种简单的约定进行有效的避免。
如在接口名称前加公司名称或产品名称。
如产品Xyz的IFly接口,可以使用IXyzFly接口名称。
2、函数名称的冲突亦可类似接口名称方法进行解决。
如IXyzFly::Fly与IAbcFly::Fly,
可以改为:IXyzFly::XyzFly与IAbcFly:: AbcFly
3、不使用多重接口继承。
4、使用包容与聚合技术。
注:COM接口是一个二进制内存标准,客户同接口的连接是通过接口在其表示的内存块中的位置完成的,而不是通过接口名称
或其成员函数名称完成。因此COM并不关心接口的名称或其成员函数的名称。
【7】接口的不变性
接口的不变性是指一旦接口被公布,那么它将永远保持不变。
1、对组件升级时,一般不会修改原有的接口,而是根据需要适当的增加一些新的接口来扩展其原有的功能。
2、通过使用多重接口的继承技术,使得这些组件不但能够支持原有的接口,还可以支持新的接口。
因此多重继承为组件和客户可以智能对同对方的新版本进行交互打下了扎实的基础。
【8】接口的多态性
多态性是指可以按照同一种方式来处理不同的对象。
若两个不同的组件支持同一个接口,那么客户可以使用相同的代码来处理其中任何的一个组件,也就是说,客户可以使用相同的方式来处理不同的组件,从而实现了接口的多态性。
多重接口的支持能力为接口的多态提供了更多的机会,也使得多态的重要性更为突出。因为多态性可以使代码更加有效的被复用。
【9】COM接口内存结构
COM接口的内存结构同C++编译器为抽象基类所生成的内存结构是相同的。
如下纯抽象基类:
1 interface IX
2 {
3 virtual void __stdcall Fx1() = 0;
4 virtual void __stdcall Fx2() = 0;
5 virtual void __stdcall Fx3() = 0;
6 virtual void __stdcall Fx4() = 0;
7 };
如下抽象基类所定义的内存接口示例:
一个纯抽象基类所定义的内存结构分为两部分。
左边是一个指向虚函数表的指针(简称为vtbl指针)
右边是一个虚拟函数表,其中包含一组指向虚拟函数实现的指针。
而指向抽象基类的指针将指向vtbl指针。
而所有的COM接口都必须继承一个名为IUnknown的接口,这意味着所有的COM接口的前三个项都是相同的,其中保存的是IUnknown中三个成员函数实现的地址。
备注:接口的内存结构在不同的操作系统上可能不同。
【10】vtbl指针的作用
vtbl指针在由pIX接口指针到虚拟函数表的过程中,看上去增加了一个额外的级别,有什么作用呢?
(1) C++编译器生成代码时,实现抽象基类的派生类会将特定的实例的信息(如成员数据)同vtbl一起保存。
假设类CA派生于IX,并拥有自己的成员数据m_dA。
单个实例时:pA = new CA;
pA内存结构图如下(实例数据以黑色背景表示):
(2)同一个类的不同实例可以共享同一个vtbl
若我们建立CA两个不同的实例,那么将会有两组不同的实例数据。
但不同的实例可以共享同一vtbl以及相同的实现。
如下代码:
1 void main()
2 {
3 //Create first instance of CA
4 CA* pA1 = new CA(1.5);
5 //Create second instance of CA
6 CA* pA2 = new CA(2.75);
7 }
同一类的多个实例共享vtbl,如下图:
(3) 多态
代码如下:
1 #include <iostream>
2 using namespace std;
3 #include <objbase.h>
4
5 interface IX
6 {
7 virtual void __stdcall Fx1() = 0;
8 virtual void __stdcall Fx2() = 0;
9 virtual void __stdcall Fx3() = 0;
10 virtual void __stdcall Fx4() = 0;
11 };
12
13 class CA : public IX
14 {
15 public:
16 virtual void __stdcall Fx1() { cout << "CA::Fx1" << endl; }
17 virtual void __stdcall Fx2() { cout << m_dFx2 << endl; }
18 virtual void __stdcall Fx3() { cout << m_dFx3 << endl; }
19 virtual void __stdcall Fx4() { cout << m_dFx4 << endl; }
20
21 CA(double dValue = 0)
22 : m_dFx2(dValue * dValue)
23 , m_dFx3(dValue * dValue * dValue)
24 , m_dFx4(dValue * dValue * dValue * dValue)
25 {}
26
27 private:
28 double m_dFx2;
29 double m_dFx3;
30 double m_dFx4;
31 };
32
33 class CB : public IX
34 {
35 public:
36 virtual void __stdcall Fx1() { cout << "CB::Fx1" << endl; }
37 virtual void __stdcall Fx2() { cout << "CB::Fx2" << endl; }
38 virtual void __stdcall Fx3() { cout << "CB::Fx3" << endl; }
39 virtual void __stdcall Fx4() { cout << "CB::Fx4" << endl; }
40 };
41
42 void Fun(IX* pIx)
43 {
44 pIx->Fx1();
45 pIx->Fx2();
46 pIx->Fx3();
47 pIx->Fx4();
48 }
49
50 void main()
51 {
52 //create instance of CA
53 CA* pA = new CA(10);
54 //create instance of CB
55 CB* pB = new CB;
56 //get IX Pointer to CA
57 IX* pIx = pA;
58 Fun(pIx);
59 //get IX Pointer to CB
60 pIx = pB;
61 Fun(pIx);
62 }
63
64 //Output
65 /*
66 CA::Fx1
67 100
68 1000
69 10000
70 CB::Fx1
71 CB::Fx2
72 CB::Fx3
73 CB::Fx4
74 */
通过一个共同的抽象基类来一致的使用两个不同的类,内存结构如下图:
备注:
1、上图的实例数据用空框表示,因为COM一般都不关心实例数据。
2、两个虚拟表的结构相同,即两个表中的函数Fxi所占据的表格项都在第i项。
Good Good Study, Day Day Up.
顺序 选择 循环 总结