COM编程之三 QueryInterface

COM编程之三 QueryInterface【1】IUnknown接口客户同组件交互都是通过接口完成的。在客户查询组件的其它接口时,也是通过接口完成的。而那个接口就是IUnknown。IUnknown接口的定义包含在Win32SDK中的U

大家好,又见面了,我是你们的朋友全栈君。

【1】IUnknown接口

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

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

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

1 interface IUnknown 2 { 3     virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv0 = 0; 4     virtual ULONG __stdcall AddRef() = 0; 5     virtual ULONG __stdcall Release() = 0; 6 };

【2】COM接口内存结构

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

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

因此每个COM接口都支持QueryInterface

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

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

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

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

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

<span role="heading" aria-level="2">COM编程之三 QueryInterface

【3】QueryInterface函数

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

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

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

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

QueryInterface函数原型如下:

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

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

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

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

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

 1 void  Fun(IUnknown* pl)  2 {  3     //Define a pointer for the interface
 4     IX* pIx = NULL;  5     //Ask for interface IX
 6     HRESULT hr = pl->QueryInterface(IID_IX, (void**)&pIx);  7     //Check return value
 8     if (SUCCEEDED(hr))  9  { 10         //Use interface
11         pIx->Fx1(); 12  } 13 }

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

完整代码如下:

 1 #include <iostream>  2 using namespace std;  3 #include <objbase.h>  4  5 void trace(const char* msg)  6 {  7 cout << msg << endl;  8 }  9  10 // 接口定义  11 interface IX : IUnknown  12 {  13 virtual void __stdcall Fx() = 0;  14 };  15  16 interface IY : IUnknown  17 {  18 virtual void __stdcall Fy() = 0;  19 };  20  21 interface IZ : IUnknown  22 {  23 virtual void __stdcall Fz() = 0;  24 };  25  26 // Forward references for GUIDs  27 extern const IID IID_IX;  28 extern const IID IID_IY;  29 extern const IID IID_IZ;  30  31 //  32 // 实现接口 IX, IY(这里表示一个组件)  33 //  34 class CA : public IX, public IY  35 {  36 //IUnknown implementation  37 virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);  38 virtual ULONG __stdcall AddRef() { return 0;}  39 virtual ULONG __stdcall Release() { return 0;}  40  41 // Interface IX implementation  42 virtual void __stdcall Fx() { cout << "这里是Fx函数" << endl;}  43  44 // Interface IY implementation  45 virtual void __stdcall Fy() { cout << "这里是Fy函数" << endl;}  46 };  47  48 HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv)  49 {  50 if (iid == IID_IUnknown)  51  {  52 trace("QueryInterface: Return pointer to IUnknown.");  53 *ppv = static_cast<IX*>(this);  54  }  55 else if (iid == IID_IX)  56  {  57 trace("QueryInterface: Return pointer to IX.");  58 *ppv = static_cast<IX*>(this);  59  }  60 else if (iid == IID_IY)  61  {  62 trace("QueryInterface: Return pointer to IY.");  63 *ppv = static_cast<IY*>(this);  64  }  65 else  66  {  67 trace("QueryInterface: Interface not supported.");  68 *ppv = NULL;  69 return E_NOINTERFACE;  70  }  71 reinterpret_cast<IUnknown*>(*ppv)->AddRef(); // 加计数  72 return S_OK;  73 }  74  75 //  76 // 创建类CA,并返回一个指向IUnknown的指针  77 //  78 IUnknown* CreateInstance()  79 {  80 IUnknown* pI = static_cast<IX*>(new CA);  81 pI->AddRef();  82 return pI ;  83 }  84  85 //  86 // 下面是各接口的IID  87 //  88 // {32bb8320-b41b-11cf-a6bb-0080c7b2d682}  89 static const IID IID_IX =  90 {0x32bb8320, 0xb41b, 0x11cf,  91 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};  92  93 // {32bb8321-b41b-11cf-a6bb-0080c7b2d682}  94 static const IID IID_IY =  95 {0x32bb8321, 0xb41b, 0x11cf,  96 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};  97  98 // {32bb8322-b41b-11cf-a6bb-0080c7b2d682}  99 static const IID IID_IZ = 100 {0x32bb8322, 0xb41b, 0x11cf, 101 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; 102 103 // 104 // 主函数(这里代表客户) 105 // 106 int main() 107 { 108  HRESULT hr; 109 110 trace("Client:获取 IUnknown指针."); 111 IUnknown* pIUnknown = CreateInstance(); 112 113 trace("Client:获取接口IX."); 114 115 IX* pIX = NULL; 116 hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX); 117 if (SUCCEEDED(hr)) 118  { 119 trace("Client:获取接口IX成功."); 120 pIX->Fx(); // 使用 IX. 121  } 122 123 trace("Client:获取接口IY."); 124 125 IY* pIY = NULL; 126 hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY); 127 if (SUCCEEDED(hr)) 128  { 129 trace("Client: Succeeded getting IY."); 130 pIY->Fy(); // 使用 IY. 131  } 132 133 trace("Client:是否支持接口IZ."); 134 135 IZ* pIZ = NULL; 136 hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ); 137 if (SUCCEEDED(hr)) 138  { 139 trace("Client:获取接口IZ成功."); 140 pIZ->Fz(); 141  } 142 else 143  { 144 trace("Client:获取接口IZ失败,不支持接口IZ."); 145  } 146 147 trace("Client:用接口IX查询接口IY."); 148 149 IY* pIYfromIX = NULL; 150 hr = pIX->QueryInterface(IID_IY, (void**)&pIYfromIX); 151 if (SUCCEEDED(hr)) 152  { 153 trace("Client:获取接口IY成功."); 154 pIYfromIX->Fy(); 155  } 156 157 trace("Client:用接口IY查询接口IUnknown."); 158 159 IUnknown* pIUnknownFromIY = NULL; 160 hr = pIY->QueryInterface(IID_IUnknown, (void**)&pIUnknownFromIY); 161 if (SUCCEEDED(hr)) 162  { 163 cout << "IUnknown指针是否相等?"; 164 if (pIUnknownFromIY == pIUnknown) 165  { 166 cout << "Yes, pIUnknownFromIY == pIUnknown." << endl; 167  } 168 else 169  { 170 cout << "No, pIUnknownFromIY != pIUnknown." << endl; 171  } 172  } 173 174 // Delete the component. 175  delete pIUnknown; 176 177 return 0; 178 } 179 180 //Output 181 /* 182 Client:获取 IUnknown指针. 183 Client:获取接口IX. 184 QueryInterface: Return pointer to IX. 185 Client:获取接口IX成功. 186 这里是Fx函数 187 Client:获取接口IY. 188 QueryInterface: Return pointer to IY. 189 Client: Succeeded getting IY. 190 这里是Fy函数 191 Client:是否支持接口IZ. 192 QueryInterface: Interface not supported. 193 Client:获取接口IZ失败,不支持接口IZ. 194 Client:用接口IX查询接口IY. 195 QueryInterface: Return pointer to IY. 196 Client:获取接口IY成功. 197 这里是Fy函数 198 Client:用接口IY查询接口IUnknown. 199 QueryInterface: Return pointer to IUnknown. 200 IUnknown指针是否相等?Yes, pIUnknownFromIY == pIUnknown. 201 */

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

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

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

例如:

 1 interface IX  2 {  3 virtual void __stdcall Fx1() = 0;  4 virtual void __stdcall Fx2() = 0;  5 virtual void __stdcall Fx3() = 0;  6 virtual void __stdcall Fx4() = 0;  7 };  8  9 interface IY 10 { 11 virtual void __stdcall Fy1() = 0; 12 virtual void __stdcall Fy2() = 0; 13 virtual void __stdcall Fy3() = 0; 14 virtual void __stdcall Fy4() = 0; 15 }; 16 17 class CA : public IX, public IY 18 { 19 virtual void __stdcall Fx1() { cout << "IX::Fx1" << endl;} 20 virtual void __stdcall Fx2() { cout << "IX::Fx2" << endl;} 21 virtual void __stdcall Fx3() { cout << "IX::Fx3" << endl;} 22 virtual void __stdcall Fx4() { cout << "IX::Fx4" << endl;} 23 24 virtual void __stdcall Fy1() { cout << "IY::Fy1" << endl;} 25 virtual void __stdcall Fy2() { cout << "IY::Fy2" << endl;} 26 virtual void __stdcall Fy3() { cout << "IY::Fy3" << endl;} 27 virtual void __stdcall Fy4() { cout << "IY::Fy4" << endl;} 28 }; 29 30 void FunX(IX* pIx) 31 { 32 cout<<"pIx:"<<" "<< pIx <<endl; 33 pIx->Fx1(); 34 pIx->Fx2(); 35 pIx->Fx3(); 36 pIx->Fx4(); 37 } 38 39 void FunY(IY* pIy) 40 { 41 cout<<"pIy:"<<" "<< pIy <<endl; 42 pIy->Fy1(); 43 pIy->Fy2(); 44 pIy->Fy3(); 45 pIy->Fy4(); 46 } 47 48 void main() 49 { 50 CA* pA = new CA; 51 cout<<"pA:"<<" "<<pA<<endl; 52 53  FunX(pA); 54  FunY(pA); 55 56  delete pA; 57 pA = NULL; 58 } 59 60 //Output 61 /* 62 pA: 00494B80 63 pIx: 00494B80 64 IX::Fx1 65 IX::Fx2 66 IX::Fx3 67 IX::Fx4 68 pIy: 00494B84 69 IY::Fy1 70 IY::Fy2 71 IY::Fy3 72 IY::Fy4 73 */

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

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

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

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

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

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

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

<span role="heading" aria-level="2">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的实现不遵循此规则,将无法决定两个接口是否属于同一组件。

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

 1 BOOL IsSameComponent(IX* pIx, IY* pIy)  2 {  3 IUnknown* pI1 = NULL;  4 IUnknown* pI2 = NULL;  5 //Get IUnknown pointer from pIx  6 pIx->QueryInterface(IID_IUnknown, (void**)&pI1);  7 //Get IUnknown pointer from pIy  8 pIy->QueryInterface(IID_IUnknown, (void**)&pI2);  9 //Are the two IUnknown pointer equal ? 10 return pI1 == pI2; 11 }

(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,既支持组件新的接口也支持老的接口。

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

<span role="heading" aria-level="2">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.

顺序  选择  循环  总结

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154968.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 联想笔记本电脑键盘亮了屏幕不亮_笔记本电脑开机键盘亮了屏幕不亮

    联想笔记本电脑键盘亮了屏幕不亮_笔记本电脑开机键盘亮了屏幕不亮联想笔记本电脑一直是笔记本电脑行业的大品牌,深受人们的喜爱。然而,它在使用时也会遇到很多问题,比如如何处理联想笔记本电脑开机键亮但是黑屏?联想笔记本电脑键盘失灵怎么办?因此,在购买联想笔记本之前,我们需要清楚地知道如何解决这个问题。一、联想笔记本电脑开机键亮但是黑屏1.如果联想笔记本电脑的开机键很亮,但屏幕是黑色的,您可以卸下笔记本电脑背面的电池,仅将电脑与电源适配器连接,然后尝试开机一次。如果启…

  • tomcat jvm优化

    tomcat jvm优化tomcat经常挂机没反应,发现PSPermGen的使用率一直在99%。经常溢出优化如下:在bin/catalina.sh中添加 JAVA_OPTS=”-server-Xms800m-Xmx800m -XX:PermSize=64M-XX:MaxNewSize=256m-XX:MaxPermSize=128m-Djava.awt.headless=true”

  • 反引号 ` 用于定义多行文本

    反引号 ` 用于定义多行文本

  • 数据结构实验哈夫曼编码算法的实现_哈夫曼编码算法的实现

    数据结构实验哈夫曼编码算法的实现_哈夫曼编码算法的实现一、什么是赫夫曼编码哈夫曼编码(HuffmanCoding),又称霍夫曼编码,是一种编码方式,可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来

  • 海量数据处理技巧

    海量数据处理技巧数据时代来临,数据量的爆炸式增长是最为显著的特征。当高性能硬件的普及还跟不上这样的数据大潮时,如何在有限的时空资源内处理海量数据成为了计算机科学以及数理统计等领域最大的挑战。所谓“数据处理”,在本文中特指通过计算机技术,对海量数据进行存储、统计、查询等操作。我将在下面介绍一些基本的海量数据处理的方法,供大家参考。需要明确的一点是,现实情况复杂多变,所以对于海量数据处理这样大的主题,是不可能用一…

  • XPS文件怎么打开?可以转成PDF格式吗?

    XPS文件怎么打开?可以转成PDF格式吗?我们在打印文件时经常会遇到和我们保存的格式不一样的情况,为了无法轻易变更档案中的数据,有些朋友就将其保存为xps格式文件。下面就一起来看一下打开xps格式文件和转成PDF的方法。1、如何打开XPS文件目前很多人的电脑系统已更新的WIN10系统,其实win10是自带打开XPS文件功能软件的,操作也很方便。首先点击电脑左下角的开始菜单,在搜索框中输入XPSViewer,并点击运行软件。接着在主界面中选择要打开的文件,即可进行阅读操作。2、如何将XPS转成PDF我们可以使用一些在线转换工具,这样不

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号