对于接口,通常是采用抽象基类来定义,并利用类的多重继承来实现该组件。例如,在上面这段代码中,IX和IY是用于实现接口的抽象基类。所谓的抽象基类是只包含一个或多个虚函数声明而未包括虚函数的具体实现的类。抽象基类不能被实例化,而只能用作基类使用,并要求其派生类完成其所有虚函数的实现。在上面这段代码中,CObjectA组件即继承了IX和IY这两个抽象基类,并实现了其所定义的虚函数。图2为此组件具有的这两个接口的模型展示:
图2 接口模型
抽象基类本身由于没有实体函数与变量,所以并不分配内存。通常只是用来为派生类指定内存结构。只有在派生类实现此抽象基类时,指定的内存才会被分配。图3为此内存结构的示意:
图3 抽象基类定义的内存结构示意
图中vtable为虚拟函数表,能够为实例数据的提供一个方便保存的位置,并能够在同一类的多个实例间共享。在每个实例的内存映射中均包含一个指向该类的vtable表的指针pVtable。pVtable指针存放于所有数据成员之前,由于每个虚函数在vtable表中有唯一的索引,编译器只需根据索引从vtable表中找到函数地址即可。也就是说,客户只要获取得到了接口指针,就可以使用此COM对象的实际功能。
由抽象基类指定的内存结构是符合COM规范的,因此抽象基类IX可以认为是一个COM接口,但这还不是一个严格意义上的COM接口。对于一个真正意义上的COM接口,在设计时应遵循以下几个规则:
1) 接口必须直接或间接地从IUnknown继承。
2) 接口必须具有唯一的标识(IID)。
3) 一旦分配和公布了IID,有关接口定义的任何因素都不能被改变。
4) 接口成员函数应具有HRESULT类型的返回值。
5) 接口成员函数的字符串参数应采用Unicode类型。
这几条规则中,最基本的是第一条,如果一个对象没有至少实现一个最小程度为IUnknown的接口,那么该对象也就不是一个严格的COM对象。IUnknown接口是COM的核心接口,从上述规则可以得知,任何一个COM接口都必须从IUnknown接口继承。客户在组件之间的通信是通过接口来实现的。组件可以不提供其他接口,但是必须提供IUnknown接口以使客户能够对组件其他接口进行查询。
IUnknown接口提供有成员函数QueryInterface()、AddRef()和Release(),分别用于查询组件中的其他接口和进行生存期控制。由于任何COM接口都是从IUnknown接口派生,因此在所有COM接口虚拟函数表中保存的前三个成员函数指针一定是指向QueryInterface()、AddRef()和Release()的指针。这样,任何一个COM接口都可以被当作IUnknown接口来处理。在创建组件时,客户可以通过CreateInstance()函数得到IUnknown接口指针。