2.1 再谈接口与实现
把接口与实现分离的动机是允许实现类的内部的数据成员的数量和顺序都可以发生变化,但客户无需重新编译,并且允许DLL和客户不必使用同样的C++编译器。前面讲述的技术,虽然提供了编译器独立性,但还不足以为二进制组建提供一个普遍的底层基础。为了创建一个真正的二进制组件通用底层基础,最终还的需要语言独立性。从本质上讲,我们应该从任何一种语言都可以访问这个对象,而不仅仅是实现对象所选用的语言。而上一节仅仅能在C++环境下工作。COM提供了这样一种语言,它只使用了基本的、大家熟悉的C语法,同时加入了某些用来消除C语言二义性特征的能力。这种语言被称为接口定义语言(Interface Definition Language)
2.2 IDL
COM IDL以开放软件基金会(open software foundation, osf)的 DCE RPC IDL为基础。com idl 只是在DCE IDL的基础上,加了一些与COM相关的扩展,以便支持COM中的面向对象的特性(继承和多态)。
IDL的理解:逻辑角度,对接口的方法以及他们所执行的操作的讨论;物理角度,对内存、堆栈结构、网络数据包获知其他运行时现象的讨论。
IDL允许接口设计者使用C风格的语法,但设计者的主要工作却在逻辑领域。IDL允许通过使用属性精确的指定接口的任何方面。Idl属性位于方括号中,多个属性之间用“,”分隔符隔开,即所谓的属性列表。如:
[v1_enum, helpstring("this is a color!")]
enum COLOR {RED, GREEN, BLUE};
v1_enum属性应用到枚举COLOR,告诉IDL编译器COLOR的网络表示形式为32位而不是16位(default)。helpstring,应用到COLOR上,在最终产生的文件中插入自负串“this is a color!”。Idl如果没有属性,就简单为C语言了(struct union ,数组, enum等)。
在使用IDL定义COM方法时,我们必须显示表明方法参数的读出和写入,使用属性[in]和[out]来完成。
void method([int] long arg1,
[out] long* arg2,
[int, out] long* arg3);
long arg2 =20, arg3 = 30;
p->method(10, &arg2, &arg3);
2.3 方法和效果
COM的方法都会返回一个HRESULT类型的错误号,在对java而言,HRESULT被Java虚拟机截获,然后抛出异常。
严重程度指示位,表明操作成功还是失败;操作码指示了HRESULT对应的什么技术;信息码矢在给定前两者之后精确的结果值。两个宏可以确定成功与否:
#define SUCCEEDED(hr) (long(hr) >= 0)
#define FAILED(hr) (long(hr) < 0)
为了返回一个与“方法的物理HRESULT”不相关的逻辑结果,COM IDL支持reval属性,其含义是:方法参数实际上是操作的逻辑结果,在支持retval的环境中,该参数被映射为操作的结果。例如:
HRESULT Method([int] short arg1, [out, retval] short *parg2);
public short Method(short arg1);//java
virtual HRESULT _stdcall Method2(short arg1, short* parg2) = 0;//C++没有专门的运行时库支持COM接口的访问操作,Microsoft c++把方法映射为此
C++的客户端代码:
short sum = 10;
short s;
HRESULT hr = pItf->Method2(20, &s);
if(FAILED(hr))
throw hr;
sum += s;
2.4 接口和IDL
IDL是经过简单注记的C函数原型。因为C没有对接口支持,IDL需要对c进行扩展,因此定义interface关键字。接口有4部分构成:接口名,父接口名,接口体(一组方法定义的简单集合,并且支持类型定义语句)和接口属性。
[attribute1, attribute2,...]
interface IThisInterface:IBaseInterface{
typedef1;
typedef2;
....
method1;
method2;
....
}
注意:每个COM接口必须有两个IDL属性:[object],说明该接口定义是一个COM接口,而不是DCE接口;第二个属性需指明接口的实质名字(前面的IThisInterface是接口的逻辑名字,主要是为防止名字冲突)。针对第二个属性,COM接口在设计时被分配一个二进制名字,GUID(Globally Unique Identity,读squid)。GUID是128位的大数,可以保证时间和空间上两方面的唯一性,从而避免了组件之间产生的名字冲突。函数 HRESALUT CoCreateGuid(GUID *pguid);。为了把接口的实质名字和它的IDL中的定义联系起来,我们要使用[uuid]属性。
[object, uuid(983CE3CB-76EC-4A3B-9A34-523632D83FA4)]
interface ICalculator : IBaseInterface{
HRESULT Clear(void);
HRESULT Add ([in] long n);
HRESULT Sum ([out, retval] long* pn);
}
在编写C/C++程序时,当涉及接口的实质名时,接口的IID只是:IID_interfaceName如,IID_ICalculator。为了方便存储和访问,COM定义了一个C结构,用来表示GUID的128位值。
typedef struct _GUID{
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
}GUID;
typedef GUID IID;
typedef GUID CLSID;
#define REFGUID const GUID&
#define REFIID const IID&
#define REFCLSID const CLSID&
并且COM提供了对GUID引用类型的重载“==”“!=”。