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)


相关推荐

  • VS Code关闭eslint校验

    VS Code关闭eslint校验一、产生原因:在编写vue代码的时候,一直因为格式问题报错,按照要求改了格式,虽不报错,但当选择格式化文档,就会再次报错,所以需要关闭格式校验功能。二、解决办法:①:若报错,可将鼠标放在报错位置,按照提示内容,单机右键,选择快速恢复;但后期影响继续存在②:关闭校验功能步骤:1.点击左下角的设置图标并选择设置2.搜索eslint,如图并勾选可取消报错:3.重启VSCode,编译时不再报错…

  • 最简单也最难——怎样获取到Android控件的高度「建议收藏」

    最简单也最难——怎样获取到Android控件的高度

  • Intellij IDEA优化配置(1)——Darcula主题的选择以及字体和颜色配置(基于Intellij IDEA 2019.1)

    Intellij IDEA优化配置(1)——Darcula主题的选择以及字体和颜色配置(基于Intellij IDEA 2019.1)Darcula主题的选择以及字体和颜色配置IntellijIDEA优化配置一.主题选择二.主题导入合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程…

  • matlab资产组合最优配置_最优投资组合怎么确定

    matlab资产组合最优配置_最优投资组合怎么确定您所在位置:网站首页>海量文档&nbsp>&nbsp计算机&nbsp>&nbspmatlab基于MATLAB的最优投资组合问题.pdf3页本文档一共被下载:次,您可全文免费在线阅读后下载本文档。下载提示1.本站不保证该用户上传的文档完整性,不预览、不比对内容而直接下载产生的反悔问题本站不予受理。2.该文档所得收…

    2022年10月28日
  • FZU2127:养鸡场

    FZU2127:养鸡场

  • 苹果系统自带的计算机怎么恢复出厂设置,苹果电脑MacBook如何将系统恢复出厂设置…

    苹果系统自带的计算机怎么恢复出厂设置,苹果电脑MacBook如何将系统恢复出厂设置…‍有时候在使用电脑的过程中会出现问题,需要进行恢复出厂设置的操作。如果使用的是苹果电脑MacBookAir,那么具体应该怎么操作呢?大家不妨参考接下来介绍的具体方法。1、首先开启Mac的电源开关,同时按住command+R键进入恢复模式,就可以看到MacOSX实用工具;2、选择磁盘工具–然后选择上面一般是MacintoshHD这个选项,右侧选择抹掉,意思就是删除所有内容重新安装新的系统;…

发表回复

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

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