第三章
本章讨论了客户如何向组件询问它所支持的接口,组件如何回答,以及这种请求应答方式的结果。
客户同组件交互都是通过接口完成的。在客户程序查询组件的其他接口时,也是通过接口完成的,因此每一个COM组件必须实现一个共同的接口,供客户程序和组件通信,
这个接口就是IUnknown。IUnknown的定义在Win32 SDK的UNKNWN.H头文件中,定义如下:
interface IUnknown { virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv) = 0; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; }
所有的COM接口都必须继承并实现IUnknown接口,每个COM接口的前三个函数都是QueryInterface,AddRef,Release,如果不是的话,它就不是一个COM接口。因为接口
都是在COM组件的内部实现的,因此客户程序可以使用任何一个接口的QueryInterface函数来获取组件所支持的其它接口(例如可通过IX接口获取同一个组件实现的IY接口)。
IX接口的内存图
QueryInterface的第一个参数是接口标识符IID,第二个参数用来返回所查询的接口指针,HRESULT返回值使用SUCCEEDED或FAILED宏验证是否成功。
在本章,作者在客户端使用了IUnknown *CreateInstance()函数来获取创建并获取组件的IUnkown接口指针。这并不是建立COM组件的真正方法,第6,7章会介绍到。本章的例子分为三个部分,组件的接口定义,组件的实现,main函数客户部分。
//IUnkown.cpp //use:cl IUnkown.cpp UUID.lib // #include <iostream> #include <string> #include <objbase.h> using namespace std; void trace(string msg) { cout<<msg<<endl; } //Interfaces interface IX:IUnknown { virtual void __stdcall Fx() = 0; }; interface IY:IUnknown { virtual void __stdcall Fy() = 0; }; interface IZ:IUnknown { virtual void __stdcall Fz() = 0; }; extern const IID IID_IX ; extern const IID IID_IY ; extern const IID IID_IZ ; //Component class CA:public IX, public IY { virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv); virtual ULONG __stdcall AddRef() { return 0; } virtual ULONG __stdcall Release() { return 0; } virtual void __stdcall Fx() { cout<<"Fx"<<endl; } virtual void __stdcall Fy() { cout<<"Fy"<<endl; } }; HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv) { if(iid == IID_IUnknown) { trace("Return pointer to IUnkown"); *ppv = static_cast<IX*>(this); } else if(iid == IID_IX) { trace("pointer to IX"); *ppv = static_cast<IX*>(this); } else if(iid == IID_IY) { trace("return pointer to IY"); *ppv = static_cast<IY*>(this); } else { trace("Interface not supported"); *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } //Create function IUnknown *CreateInstance() { IUnknown *pI = static_cast<IX*>(new CA); pI->AddRef(); return pI; } // IIDs // {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}} ; /////////////////////////////////////////////////////// //Client /////////////////////////////////////////////////////// int main(void) { HRESULT hr; trace("Client: Get Iunkown pointer"); IUnknown *pIunknown = CreateInstance(); trace("Client: Get interface IX"); IX *pIx = NULL; hr = pIunknown->QueryInterface(IID_IX, (void**)&pIx); if(SUCCEEDED(hr)) { trace("Client:Succeeded get IX"); pIx->Fx(); } trace("Client: Get interface IY"); IY *pIy = NULL; hr = pIunknown->QueryInterface(IID_IY, (void**)&pIy); if(SUCCEEDED(hr)) { trace("Client: Succeeded get IY"); pIy->Fy(); } trace("Client: Ask for an unsupported interface"); IZ *pIz = NULL; hr = pIunknown->QueryInterface(IID_IZ, (void**)&pIz); if(SUCCEEDED(hr)) { trace("Client: Succeeded get IZ"); pIz->Fz(); } else { trace("Client: Could not get interface IZ"); } trace("Client: Get interface IUnkown from IY"); IY *pIyFromIx = NULL; hr = pIx->QueryInterface(IID_IY, (void**)&pIyFromIx); if(SUCCEEDED(hr)) { trace("Client: Succeeded get Iy"); pIyFromIx->Fy(); } IUnknown *pIunknownFromIy = NULL; hr = pIy->QueryInterface(IID_IUnknown, (void**)&pIunknownFromIy); if(SUCCEEDED(hr)) { if(pIunknownFromIy == pIunknown) { cout<<"pIupIunkownFromIy == pIunkown"<<endl; } else { cout<<"pIupIunkownFromIy != pIunkown"<<endl; } } delete pIunknown; return 0; }
本章有几个需要注意的问题:
1.class CA: public IX, public IY
这里不能用虚拟多重继承的方式,因为这样会破坏接口的内存结构。同时static_cast<IUnknown*>(this)是不确定的,因为IX和IY接口都是从IUnknown继承得到的。
CA的内存结构如下:
这里面涉及到一个C++类的内存布局,vtbl指针在类的内存中是排在最前面的。从图中也可以发现,IX接口的IUnknown跟继承自IY接口的IUnknown的地址是不一样的,static_cast<IUnknow*>(this)是不明确的。但是它们存放QueryInterface,AddRef,Release函数地址是相同的
2.返回接口集的问题
对客户来说,它只需要关心它需要的组件接口,返回组件所支持的所有接口并没有必要。但是QueryInterface一个一个的查询,比较耗费时间,为此,可以使用一个组件类别来标识一个接口集,将在第6章中详细讨论。
3.接口升级的问题
一个IID对应一个接口,如果需要添加或者更改老接口中的函数,需要重新定义接口,并指定一个新的IID,而不是在老接口中添加函数,新接口可以继承自老接口。新接口的命名最好是老接口名字后加上数字,例如IFly,新接口就是IFly2。
4.QueryInterface有几条规则,需要看一下。