COM编程之三 QueryInterface

【1】IUnknown接口

客户同组件交互都是通过接口完成的。

在客户查询组件的其它接口时,也是通过接口完成的。而那个接口就是IUnknown。

IUnknown接口的定义包含在Win32SDK中的UNKNEN.h头文件中。引用如下:

 interface IUnknown
{
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv0 = ;
virtual ULONG __stdcall AddRef() = ;
virtual ULONG __stdcall Release() = ;
};

【2】COM接口内存结构

所有的COM接口都继承自IUnknown接口。

所以每个COM接口的vtbl中的前三个函数都是相同的。

因此每个COM接口都支持QueryInterface

从而组件的任何一个COM接口都可以被客户用来获取它所支持的其它COM接口。

同时所有的接口也将是IUnknown接口指针。

进一步而言,客户并不需要单独维护一个代表组件的指针,它所关心的仅仅是接口指针。

如果某个接口的vtbl中的前三个函数不是这个三个,那么它将不是一个COM接口。

COM接口内存结构如下图所示:

COM编程之三  QueryInterface

【3】QueryInterface函数

IUnknown中包含一个名称为QueryInterface的成员函数。

客户可以通过此函数来查询某组件是否支持某个特定的接口。

若支持,QueryInterface函数将返回一个指向此接口的指针。

否则,返回值将是一个错误代码。

QueryInterface函数原型如下:

HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);

第一个参数客户欲查询的接口的标识符。一个标识所需接口的常量。

第二个参数是存放所请求接口指针的地址

返回值是一个HRESULT值。成功为S_OK;失败为E_NOINTERFACE。

QueryInterface函数是使用。代码如下:

 void  Fun(IUnknown* pl)
{
//Define a pointer for the interface
IX* pIx = NULL;
//Ask for interface IX
HRESULT hr = pl->QueryInterface(IID_IX, (void**)&pIx);
//Check return value
if (SUCCEEDED(hr))
{
//Use interface
pIx->Fx1();
}
}

【4】一个完整的使用例子

完整代码如下:

 #include <iostream>
using namespace std;
#include <objbase.h> void trace(const char* msg)
{
cout << msg << endl;
} // 接口定义
interface IX : IUnknown
{
virtual void __stdcall Fx() = ;
}; interface IY : IUnknown
{
virtual void __stdcall Fy() = ;
}; interface IZ : IUnknown
{
virtual void __stdcall Fz() = ;
}; // 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 ;}
virtual ULONG __stdcall Release() { return ;} // 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 ;
} //Output
/*
Client:获取 IUnknown指针.
Client:获取接口IX.
QueryInterface: Return pointer to IX.
Client:获取接口IX成功.
这里是Fx函数
Client:获取接口IY.
QueryInterface: Return pointer to IY.
Client: Succeeded getting IY.
这里是Fy函数
Client:是否支持接口IZ.
QueryInterface: Interface not supported.
Client:获取接口IZ失败,不支持接口IZ.
Client:用接口IX查询接口IY.
QueryInterface: Return pointer to IY.
Client:获取接口IY成功.
这里是Fy函数
Client:用接口IY查询接口IUnknown.
QueryInterface: Return pointer to IUnknown.
IUnknown指针是否相等?Yes, pIUnknownFromIY == pIUnknown.
*/

【5】多重继承及类型转换

一般将一种类型的指针转换成另外一种类型的指针并不会改变它的值。

但是为了支持多重继承,在某些情况下,C++必须改变类指针的值。

例如:

 interface IX
{
virtual void __stdcall Fx1() = ;
virtual void __stdcall Fx2() = ;
virtual void __stdcall Fx3() = ;
virtual void __stdcall Fx4() = ;
}; interface IY
{
virtual void __stdcall Fy1() = ;
virtual void __stdcall Fy2() = ;
virtual void __stdcall Fy3() = ;
virtual void __stdcall Fy4() = ;
}; class CA : public IX, public IY
{
virtual void __stdcall Fx1() { cout << "IX::Fx1" << endl;}
virtual void __stdcall Fx2() { cout << "IX::Fx2" << endl;}
virtual void __stdcall Fx3() { cout << "IX::Fx3" << endl;}
virtual void __stdcall Fx4() { cout << "IX::Fx4" << endl;} virtual void __stdcall Fy1() { cout << "IY::Fy1" << endl;}
virtual void __stdcall Fy2() { cout << "IY::Fy2" << endl;}
virtual void __stdcall Fy3() { cout << "IY::Fy3" << endl;}
virtual void __stdcall Fy4() { cout << "IY::Fy4" << endl;}
}; void FunX(IX* pIx)
{
cout<<"pIx:"<<" "<< pIx <<endl;
pIx->Fx1();
pIx->Fx2();
pIx->Fx3();
pIx->Fx4();
} void FunY(IY* pIy)
{
cout<<"pIy:"<<" "<< pIy <<endl;
pIy->Fy1();
pIy->Fy2();
pIy->Fy3();
pIy->Fy4();
} void main()
{
CA* pA = new CA;
cout<<"pA:"<<" "<<pA<<endl; FunX(pA);
FunY(pA); delete pA;
pA = NULL;
} //Output
/*
pA: 00494B80
pIx: 00494B80
IX::Fx1
IX::Fx2
IX::Fx3
IX::Fx4
pIy: 00494B84
IY::Fy1
IY::Fy2
IY::Fy3
IY::Fy4
*/

由于CA同时继承了IX和IY,因此在可以使用IX或IY指针的地方均可以使用指向CA的指针。

FunX需要一个指向合法的IX的虚函数表的指针。

FunY则需要一个指向IY虚函数表的指针。

而IX和IY的虚函数表中的内容是不一样的。

编译器将同一指针传给FunX和FunY是不可能的。

必须对CA的指针进行修改以便它指向一个合适的vtbl指针。

同时继承IX和IY的类CA的内存结构,如图所示:

COM编程之三  QueryInterface

由示例代码运行结果以及上图可知:

CA的this指针指向IX的虚函数表。所以可以不改变CA的this指针用它来代替IX指针。

CA的this指针没有指向IY的虚函数表指针。所以在将指向类CA的指针传给一个接收IY指针的函数之前,其值必须修改。

编译器将把IY虚拟函数表指针的偏移量(△IY)加到CA的this指针上。

IY* pC = pA;

与之等价代码:

IY* pC = (char*)pA + △IY;

【6】QureryInterface的实现规则有哪些?

(1)QureryInterface返回的总是同一IUnkown地址。

如果QureryInterface的实现不遵循此规则,将无法决定两个接口是否属于同一组件。

判断两个接口是否属于同一个组件的代码实现如下:

 BOOL IsSameComponent(IX* pIx, IY* pIy)
{
IUnknown* pI1 = NULL;
IUnknown* pI2 = NULL;
//Get IUnknown pointer from pIx
pIx->QueryInterface(IID_IUnknown, (void**)&pI1);
//Get IUnknown pointer from pIy
pIy->QueryInterface(IID_IUnknown, (void**)&pI2);
//Are the two IUnknown pointer equal ?
return pI1 == pI2;
}

(2)若客户曾经获取过某个接口,那么它将总能获取此接口。

如果客户不能获取它曾经使用过的某个接口,则说明组件的接口集是不固定的,客户也将无法通过编程的方法来决定一个

组件到底具有一些什么样的功能。

(3)客户可以再次获取已拥有的接口。

(4)客户可以返回到起始接口。

若客户拥有一个IX接口指针并成功的使用它查询了一个IY接口,那么它将可以使用此IY接口来查询一个IX接口。

换而言之,不论客户所拥有的接口是什么,它都可以获取起始时所用的接口。

(5)若能从从某个接口获取某个特定的接口,那么可以从任意接口都可以获取此接口。

(6)客户能够使用任何IUnkown接口获取该组件所支持的任何接口。

制定上述规则的目的完全是为了使QureryInterface使用起来更为简单、更富有逻辑性、更一致性以及更具有确定性。

不过幸运的是,实现上述规则并不难,并且只有组件按照这些规则正确的实现了QureryInterface时,客户才不会为此担心。

【7】客户如何知道组件支持的接口?

由于客户并不知道QureryInterface的实现,也不像C++中的拥有类的头文件,所以客户了解组件的唯一方法就是使用QureryInterface来查询。

【8】组件的新版本

当组件发布一个新的接口并被用户使用之后,此接口将绝不允许发生任何变化。

当我们要升级该接口时,可以建立一个新的接口并为它指定新的IID。

当客户用QureryInterface查询老的IID时,它将返回老的接口,而当它查询新的IID时,它将返回升级过的接口。

就QureryInterface而言,一个IID就是一个接口。接口的标识(IID)是同其版本绑在一起的。

也就是说该接口升级为新的版本,IID也需要更新。

假设有一个组件Bronce,它拥有一个IFly接口,使用该组件的客户为Pilot。 经过一段时间后,组件和客户都进行了升级。

Bronce组件升级为FastBronce,其接口也升级为IFastFly。

Pilot客户升级为FastPilot,既支持组件新的接口也支持老的接口。

下图给出了它们之间各种可能的运行组合:

COM编程之三  QueryInterface

不论按何种组合,客户和组件都能够正常运行,因此该升级是非常平滑而又无缝的,且也是非常之有效的。

【9】何时需要建立组件的新版本?

为使COM版本处理多个机制能够起作用,我们在为已有的接口制定新的IID时应该要非常谨慎。

当改变了下列任何条件之一时,都应该为接口制定新的IID:

1、接口中函数的数目。

2、接口中函数的顺序。

3、某个函数的参数。

4、某个函数的参数的顺序。

5、某个函数参数的类型。

6、函数可能的返回值。

7、函数返回值的类型。

8、函数参数的含义。

9、接口中函数的含义。

总之,只要是所做的修改如果会导致已有客户不能正常运行,都应该为接口制定新的ID。

如果能够同时修改客户和组件,则可以灵活掌握上述条款。

【10】命名组件新版本的规则

在建立了新的版本之后,也应当相应的修改其名称。

COM关于新版本名称的约定是在老的版本之后加一个数字。

如IFly新的版本名称应该是IFly2。

Good  Good  Study,  Day Day Up.

顺序  选择  循环  总结

上一篇:CentOs安装Scrapy出现error: Setup script exited with error: command ‘gcc’ failed with exit status 1错误解决方案


下一篇:Ajax提交Form表单及文件上传