命名冲突
改变某个发生冲突的函数名称即可。com对此并不关心。com接口是一个二进制标准。客户同接口的连接并不是通过其成员函数的名称完成的,而是通过它在表示它的内存块中的位置完成的。
另外一种解决办法是不使用多重继承。实现组件的类并不需要继承每一个接口。而可以合用指向实现某些接口的类的指针。
COM接口的变性
COM接口一旦公布了一个接口,那么它将永远保持不变。当对组件进行升级时,一般不会修改已有的接口,而是加入一些新的接口。这些多重接口使得组件除了可以支持原有接口之外,还可以支持新的接口。因此多重继承为组件和客户可以智能地同对方的新版本进行交互提供了坚实的基础。
多态
多态指的是可以按照同一种方式来处理不同的对象。多重接口的支持能力给多态提供了更多的机会。若两个不同的组件支持同一接口,那么客户将可以使用相同的代码来处理其中的任何一个组件。也就是说,客户可以按照相同的方式来处理不同的组件。
多重接口使得多态的重要性更为突出。一个组件所支持的接口越多,这些组件就应该越小。较小的接口表示较为简单的行为,而大的接口则表示更多的行为。一个接口所表示的行为越多,它的特定性就越强,因此它被其他组件复用的可能性将越小。对于不能复用的接口,使用此接口的客户代码也将不能复用。
使用多态的一种令人吃惊的结果就是整个应用程序都将是可复用的。这种复用整个应用架构的能力并不是随便就能出现的。再次需要精心的设计接口,以使之能够支持各种不同的实现。这不但要求接口具有较高的通用性,而且客户也应按一种比较通用的方式来使用此接口。以避免对接口的实现造成不必要的限制。对于那些并没有考虑到将来可能会出现的组件的接口及应用程序。他们将不能充分利用多态所具有的各种优点。也将无法实现对这个应用框架的复用。
开发组件软件的一个最大的问题是如何设计出具有高复用性,使用性,灵活性的接口。并考虑到将来可能会出现的新情况。
C++纯抽象基类来实现COM接口,是由于纯抽象基类所定义的内存结构可以满足COM接口的需求。当定义一个纯抽象基类时,所定义的实际上时一个内存块的结构,纯抽象基类所有实现是一些具有相同的基本结构的内存块。但此内存只是在派生类实现此抽象基类时才会被分配。但派生类继承一个抽象基类时,它将继承此内存结构。
COM接口的内存结构同C++编译器为抽象基类所生成的内存结构是相同的。
COM组件绝不会访问任何实例数据。在COM中,对一个组件的访问只能通过函数完成,而绝不能直接通过变量。这一点同我们对COM组件的定义是相符的。纯抽象基类只有虚拟函数,而没有任何实例数据。
接口的真正的威力在于继承此接口的所有类均可以被客户按同一方式进行处理。
接口可以通过封装其内部实现细节而使一个由组件构成的系统免受变化的影响。只要接口不发生变化,那么客户或组件可以在不影响整个系统正常运行的情况下自由地变化。这使得我们可以用新的组件来代替老的组件。客户也可以一致地对待实现同一接口的所有组件。
COM需要所有的接口都支持三个函数。这三个函数必须使接口的vtbl中的前三个函数。
第一个函数QueryInterface
接口查询。
客户同组件的交互都是通过一个接口完成的。在客户查询组件的其他接口时,也时通过接口完成的。这个接口就时IUnknown。IUnknown接口的定义包含在Win32SDK中的UNKNWN.H头文件中。引用如下:
interface IUnknown
{
virtual HRESULT __stdcall QueryInterface(const IID&idd,void**ppv) = 0;
virtual ULONG __stdcall AddRef() = 0;
virtual ULONG __stdcall Release() = 0;
}
在IUnknown中定义了一个名为QueryInterface的函数。客户可以调用QueryInterface来决定组件是否支持某个特定的接口。AddRef和Release,这两个函数可以用来控制接口的生命期。
所有的COM接口都需要继承IUnknown。因此若某个客户拥有一个IUnknown接口的指针,它并不需要知道它所拥有的接口指针到底时什么类型,而只需要知道此接口可以用来查询其他接口就行了。
所有COM接口都继承了IUnknown每个接口的vtbl中的前三个函数都时QueryInterface、AddRef和Release这使得所有的COM接口都可以被当成IUnknown接口来处理。因此所有的接口都支持QueryInterface。因此组件的任何一个接口都可以被客户用来获取它所支持的其他接口。由于所由的接口直接同时也将是IUnknown指针,客户并不需要单独维护一个代表组件的指针,它所关心的将仅仅是接口的指针。
IUnknown指针的获取。
使用CreateInstance的函数,它可以建立一个组件并返回一个IUnkown指针。
IUnknown * CreateInstance ();
在创建组件时,客户可以使用CreateInstance而不必再使用new操作符。
IUnknown 中包含了一个名为QueryInterface的成员函数,客户可以通过此函数来查询某个组件是否支持某个特定的接口。若支持,QueryInterface将返回一个指向此接口的指针。否则返回值将时一个错误代码。仁厚客户可以接着查询其他接口或将组件卸载。
QueryInterface带有两个参数:
HRESULT __stdcall QueryInterface(const IID&iid,void **ppv);
其中的第一个参数也标识客户所需的接口。此参数是一个“接口标识符”(IID)结构。另外一个指针是QueryInterface存放所请求接口指针的地址。
QueryInterface返回的是一个HRESULT值。此值实际上并不像其名称所表示的那样是标识某个结果的句柄。相反它是一个具有特定结构的32位值。QueryInterface可以返回S_OK或E_NOINTERFACE。客户不应将QueryInterface的返回值直接同这两个值进行比较,而应使用SUCCEEDED宏和FAULED宏。
QueryInterface的使用
假定客户有了一个指向IUnknown的指针pI,为知道相应的组件是否支持某个特定的接口,可以调用QueryInterface,并传给它一个接口标识符。若QueryInterface成功返回,那就可以使用它返回的指针了。这个过程可以用如下的代码表示:
void foo(IUnknown *PI)
{
//define a pointer for the interface
IX *pIX = NULL;
//ask for interface IX.
HRESULT hr = PI->QueryInterface(IID_IX,(void **)&pIX);
//Check return value
if(SUCCEEDED(hr))
{
//Use Interface
pIX->Fx();
}
}
上面查询了pI它是否支持由IID_IX所标识的接口。IID_IX的定义在与此组件相应的头文件中,或它也可能是在某种类型的库中定义的。