普通接口实例
#include <iostream>
using namespace
std;
#include <objbase.h> //
Define interface.
/*
输出一个字符串
*/
void trace(const char* pMsg)
{
cout << pMsg
<< endl;
}
/*
IX和IY为普通接口(非COM接口),其实它们是抽象基类
*/
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:
// Implement interface IX.
virtual void __stdcall Fx1() {cout << "CA::Fx1"
<< endl;}
virtual void __stdcall Fx2() {cout << "CA::Fx2"
<< endl;}
// Implement interface IY.
virtual void __stdcall Fy1() {cout << "CA::Fy1"
<< endl;}
virtual void __stdcall Fy2() {cout << "CA::Fy2"
<< endl;}
};
/*
客户(在这里是一个主函数)
*/
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;
}
虽然C++可以直接操作和使用实例数据,但COM组件绝不会访问任何实例数据,在COM中,对一个组件的访问只能通过函数完成,而绝不能直接通过变量.这一点同我们对COM组件的定义是相符的.纯抽象基类只有虚拟函数,而没有任何实例数据.
对于COM来说,接口是一个包含一个函数指针数组的内存结构。
接口是由没有实现细节的虚线基类实现的。
编码约定
在所有的前面都加有一个字母I, 如:IX表示的“接口X”。
而在类名称的前面所加的前缀则为C, 如CA表示“类A”。
Microsoft Win32软件开发工具(SDK)中OBJBASE.H头文件的定义:#define interface struct
使用struct的原因在于struct的成员将自动具有我公有的属性,因此不需要另外定义中加上public关键字。
标准调用
Microsoft平台上COM接口所提供的所有函数使用的均是标准的调用约定.参数数目可变的函数使用的则是C调用约定.一般人们希望接口的实现使用这些约定.但要说明的是这并不是COM的绝对需要.
在WINDEF.H中pascal的定义如下:
#define pascal __stdcall
如果读者认为将pascal这个词放在代码中会让人莫名其妙,那么可以使用OBJBASE.H中所定义的如下宏:
#define
STDMEFHODCALLTYPE __stdcall
类并非组件
用C++开发组件时不一定非用类不可。组件也可以用C来实现。一个组也可以由多个类来实现。
接口并非总是继承的
COM没有要求实现某个接口的类必须从那个接口继承,这是客户并不需要了解COM组件的继承关系。对接口的继承只不过是一种实现细节而已。
多重接口及多重继承
一个接口是一个函数集合,一个组件则是一个接口集,而一个系统则是一系列组件的集合。
接口不变性
一旦分布了一个接口,那么它将永远保持不变。当对组件进行升级时,一般不会修改已有的接口,面是加入一些新接口。
命名冲突
对于一个支持多个接口的组件,接口函数的名字出现冲突是经常会遇到的。这些种情况下,改变某个发生冲突的函数名称即可。COM对此不关心。COM接口是一个二进制标准,客户同接口的连接并不是通过其名称或其成员函数的名称完成的,而是通过它在表示它的内存块的位完成的。
解决命名冲突的另外一种 方法是不使用多重继承。
实现组件的类并不需要继承每一个接口,而可以使用指向实现某些接口的类的指针。
接口名称之间出现冲突的情况也是可能的。如果在接口和函数名称的前面加上公司名称或产品名称可以减少此种 可能性。
多态
多态指的是可以按同一种方式来处理不同的对象。若两个不同的组件支持同一接口,那么客户将可以使用相同的代码一处理其中的任何一个组件。就是说,客户可以按照相同的方式来处理不同的组件。
虚拟函数表
当定义一个纯抽象类时,所定义的实际上是一个内存块的结构.纯抽象类所有实现都是一些具有相同的基本结构的内存.
如下定义一个抽象基类。内存结构如图2-4所示:
interface IX
{
virtual void __stdcall Fx1() =
0 ;
virtual void __stdcall Fx2() =
0 ;
virtual void __stdcall Fx3() =
0 ;
virtual void __stdcall Fx4() =
0 ;
};
定义一个抽象基类也就是定义了相应的内存结构。但些内存只在派生在中实现些抽象基类时才会被分配。当派生类继承一个抽象基类时,它将继承此内存结构。
似乎是一个的偶然的巧合,COM接口的内存结构同C++编译器为抽象基类所生成的内存结构是相同的.因此可以使用抽象基类来定义COM接口.
对于一个COM接口还有其他一些需求.例如,所有的COM接口都必须继承一个名为Iunknown的接口.
Vtbl指针及实例数据
Vtbl指针在由抽象基类函数指针到函数的过程中增加了一个额外的级别。这带了很大的灵活性。
实现抽象基类的类可能会将特定于实例的信息同vtbl一块保存。如下IX的实现类CA:
class CA
: public IX
{
public:
virtual void __stdcall Fx1(){cout << "CA::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;
};
多重实例
在C++中同一个类的不同实例可以共享同一个vtbl。如下:
int main()
{
CA* pA1 = new CA(4.3);
CA* pA2 = new CA(5.1);
...
...
}
虽然COM组件可以使用vtbl指针来共享vtbl,但这一点并不是必需的。COM组件的每个实例中已有一个不同的vtbl
不同的类,相同的vtbl
接口的真正威力在于继承此接口的所有类均可以被客户按同一方式进行处理。
例子:
class CB
: public IX
{
public:
// implementatio inerface 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(4.3);
CB* pB = new CB;
IX* pIX = pA;
foo(pIX);
pIX = pB;
foo(pIX);
}
从上例中,我们将CA和CB都当成是IX接口来作用。这也是多态的一个例子。
从上图可以看出,CA 和 CB 分别具有不同的实例数据、vtbl以及实现。但是因其具有相同的而可以按相同的方式来访问。