第三章
本章讨论了客户如何向组件询问它所支持的接口,组件如何回答,以及这种请求应答方式的结果。
客户同组件交互都是通过接口完成的。在客户程序查询组件的其他接口时,也是通过接口完成的,因此每一个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有几条规则,需要看一下。