客户同组件的交互都是通过一个接口完成的。在客户查询组件的其他接口时,也是通过接口完成的。这个接口就是IUnknown。它在UNKNWN.H头文件定义 :如下
Interface IUnknown
{
virtual HRESULT __stdcall QueryInterface( REFIID riid, void ** ppvObject) = 0;
virtual ULONG __stdcall AddRef( void) = 0;
virtual ULONG __stdcall Release( void) = 0;
}
所有的COM接口都继承了IUnknown,每个接口的vtbl中的前三个函数都是QueryInterface、AddRef、Release(如图3-1)。这样所有COM接口都可以被当成IUnknown接口来处理。
由于所有的接口都是从IUnknown继承的,因此所有的接口都支持QueryInterface,所以组件的任何一个接口都可以被客户用来获取它所支持的其他接口。
QueryInterface
IUnknown的一个成员函数QueryInterface,客户可以通过此函数来查询某个组件是否支持某个特定的接口。若支持QueryInterface将返回一个指向些接口的指针,不支持返回值将是一个错误代码。
QueryInterface 有两个参数,和一个HRESULT返回值
HRESULT __stdcall QueryInterface( REFIID riid, void ** ppvObject);
第一个参数:接口标识符(IID)
第二个参数:存放所请求接口指针的地址。
返回值:查询成功返回S_OK,如果不成功则返回相应错误码。
QueryInterface的使用
void foo(IUnknown* pI)
{
// 定义一个接口指针
IX* pIX = NULL;
// 查询接口IX
HRESULT hr = pI->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
// 通过接口调用函数
pIX->Fx();
}
}
QueryInterface实现
根据某个给定的IID返回指向相应接口的指针。若组件支持客户指定的接口,那么应返回S_OK以及相应的指针。若不支持返回测返回E_NoINTERFACE并将相应的指针返回值置成NULL。
QueryInterface的实现要求可以将一种类型映射成另外一种类型的结构。如:if else 、数组、哈希表或者是树来实现,但是case语句是无法用的。因为接口标识符是一个结构而不是一个数。
注意:IX和IY不能按虚拟方式继承IUnknown。否则IX和IY的vtbl中的头三个函数指向的将不是IUnknown的三个成员函数。
HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IUnknown)
{
trace("QueryInterface: Return pointer to IUnknown.");
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IX)
{
trace("QueryInterface: Return pointer to IX.");
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IY)
{
trace("QueryInterface: Return pointer to IY.");
*ppv = static_cast<IY*>(this);
}
else
{
trace("QueryInterface: Interface not supported.");
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
IUnknown
接口标准化,在COM中有两方面的内容:一是接口基本功能的标准化,二是接口内存结构的标准化。为了保证组件接口在基本功能上的标准化,COM预定义了一个基本接口IUnknow。(在文件UNKNWN.H中定义)
Class Iunknown
{
Public:
Virtual HRESULT _stdcall QueryInterface(const IID& iid, void **ppv)=0;
Virtual HRESULT _stdcal AddRef( )=0;
Virtual HRESULT _stdcal Release( )=0;
};
显然,Iunknown接口具有3个纯虚函数。COM 要求组件的所有接口必须继承自IUnknown接口,这样就保证组件的所有接口都能提供这3个服务(函数)。
一个完整的例子
#include <iostream> using namespace std; #include <objbase.h> void trace(const char* msg) { cout << msg << endl; } // 接口定义 interface IX : IUnknown { virtual void __stdcall Fx() = 0; }; interface IY : IUnknown { virtual void __stdcall Fy() = 0; }; interface IZ : IUnknown { virtual void __stdcall Fz() = 0; }; // Forward references for GUIDs extern const IID IID_IX; extern const IID IID_IY; extern const IID IID_IZ; // // 实现接口 IX,IY(这里表示一个组件) // class CA : public IX, public IY { //IUnknown implementation virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall AddRef() { return 0;} virtual ULONG __stdcall Release() { return 0;} // Interface IX implementation virtual void __stdcall Fx() { cout << "这里是Fx函数" << endl;} // Interface IY implementation virtual void __stdcall Fy() { cout << "这里是Fy函数" << endl;} }; HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) { if (iid == IID_IUnknown) { trace("QueryInterface: Return pointer to IUnknown."); *ppv = static_cast<IX*>(this); } else if (iid == IID_IX) { trace("QueryInterface: Return pointer to IX."); *ppv = static_cast<IX*>(this); } else if (iid == IID_IY) { trace("QueryInterface: Return pointer to IY."); *ppv = static_cast<IY*>(this); } else { trace("QueryInterface: Interface not supported."); *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef(); // 加计数 return S_OK; } // // 创建类CA,并返回一个指向IUnknown的指针 // IUnknown* CreateInstance() { IUnknown* pI = static_cast<IX*>(new CA); pI->AddRef(); return pI ; } // // 下面是各接口的IID // // {32bb8320-b41b-11cf-a6bb-0080c7b2d682} static const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; // {32bb8321-b41b-11cf-a6bb-0080c7b2d682} static const IID IID_IY = {0x32bb8321, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; // {32bb8322-b41b-11cf-a6bb-0080c7b2d682} static const IID IID_IZ = {0x32bb8322, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; // // 主函数(这里代表客户) // int main() { HRESULT hr; trace("Client:获取 IUnknown指针."); IUnknown* pIUnknown = CreateInstance(); trace("Client:获取接口IX."); IX* pIX = NULL; hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX); if (SUCCEEDED(hr)) { trace("Client:获取接口IX成功."); pIX->Fx(); // 使用 IX. } trace("Client:获取接口IY."); IY* pIY = NULL; hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY); if (SUCCEEDED(hr)) { trace("Client: Succeeded getting IY."); pIY->Fy(); // 使用 IY. } trace("Client:是否支持接口IZ."); IZ* pIZ = NULL; hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ); if (SUCCEEDED(hr)) { trace("Client:获取接口IZ成功."); pIZ->Fz(); } else { trace("Client:获取接口IZ失败,不支持接口IZ."); } trace("Client:用接口IX查询接口IY."); IY* pIYfromIX = NULL; hr = pIX->QueryInterface(IID_IY, (void**)&pIYfromIX); if (SUCCEEDED(hr)) { trace("Client:获取接口IY成功."); pIYfromIX->Fy(); } trace("Client:用接口IY查询接口IUnknown."); IUnknown* pIUnknownFromIY = NULL; hr = pIY->QueryInterface(IID_IUnknown, (void**)&pIUnknownFromIY); if (SUCCEEDED(hr)) { cout << "IUnknown指针是否相等?"; if (pIUnknownFromIY == pIUnknown) { cout << "Yes, pIUnknownFromIY == pIUnknown." << endl; } else { cout << "No, pIUnknownFromIY != pIUnknown." << endl; } } // Delete the component. delete pIUnknown; return 0; }
|
QueryInterface的实现规则
QueryInterface返回的总是同一IUnknown指针。
若客户曾经获取过某个接口,那么将总能获取此接口。如果曾经不能,则将总是不能。
客户可以再次获取已经拥有的接口。
客户可以返回到接口。
若能够从某个接口获取某特定接口,那么可以从任意接口都将可以获取此接口。
同一IUnknown指针:
组件的实例只有一个IUnknown接口。因为查询组件实例的IUnknown接口时,不论通过哪个接口,所得到的均将是同一指针值。所以可以通过两个接口的IUnknown,然后比较他们的值。看看是否相同来判断两个接口是否在同一个组件里。
/*
判断两个接口是否在同一个组件里
*/
BOOL SameComponents(IX* pIX, IY* pIY)
{
IUnknown* pI1 = NULL;
IUnknown* pI2 = NULL;
pIX->QueryInterface(IID_IUnknown, (void**)&pI1);
pIY->QueryInterface(IID_IUnknown, (void**)&pI2);
return pI1 == pI2;
}
QueryInterface定义了组件
一个组件实际上就是由QueryInterface定义的。组件所支持的接口集就是QueryInterface能够为之返回接口指针的那些接口。这一点是由QueryInterface的实现决定的,而不是由实现组伯的C++类决定的。实现组件的类的继承层次关系也不能决定组件。
新版本组件的处理
COM接口是不会发性变化的,当组件发布一个接口并补某个使用后,此接口将决不会发生任何变化。如果我们想改动它。只能通过增加新的接口。
何时需要建立一个新版本
当改了下列条件中任何一个时,就应该给新接口指定新的ID.
接口中函数的数目。
接口中函数的顺序。
某个函数的参数。
某个函数参数的顺序。
某个函数参数的类型。
函数可能的返回值。
函数返回值的类型。
函数参数的含义。
接口中函数的含义。
总之,只要是所做的修改会影响客户的正常运行,都应该为接口指定新的ID