【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接口内存结构如下图所示:

【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 interface4     IX* pIx = NULL;5     //Ask for interface IX6     HRESULT hr = pl->QueryInterface(IID_IX, (void**)&pIx);7     //Check return value8     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 : IUnknown12 {13     virtual void __stdcall Fx() = 0;14 };15 16 interface IY : IUnknown17 {18     virtual void __stdcall Fy() = 0;19 };20 21 interface IZ : IUnknown22 {23     virtual void __stdcall Fz() = 0;24 };25 26 // Forward references for GUIDs27 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 IY35 {36     //IUnknown implementation37     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 implementation42     virtual void __stdcall Fx() { cout << "这里是Fx函数" << endl;}43 44     // Interface IY implementation45     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     else66     {         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 // 下面是各接口的IID87 //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 IX2 {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的内存结构,如图所示:

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

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 pIx6     pIx->QueryInterface(IID_IUnknown, (void**)&pI1);7     //Get IUnknown pointer from pIy8     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,既支持组件新的接口也支持老的接口。

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

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

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

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

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

1、接口中函数的数目。

2、接口中函数的顺序。

3、某个函数的参数。

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

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

6、函数可能的返回值。

7、函数返回值的类型。

8、函数参数的含义。

9、接口中函数的含义。

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

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

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

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

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

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

原文:https://www.cnblogs.com/Braveliu/p/3435560.html

com专栏:https://www.cnblogs.com/Braveliu/category/534745.html

COM编程之三 QueryInterface相关推荐

  1. 游戏编程之三 DirectX SDK简介

    `视频课:[免费]跨平台APP JQuery Mobile开发-1-初探移动开发-张晨光的在线视频教程-CSDN程序员研修院 第三章 DirectX  SDK简介 第一节 关于DirectX SDK ...

  2. OpenCV计算机视觉编程之三种图像像素的遍历方法

    为了构建计算机视觉应用程序,需要学会访问图像内容,有时也要修改或创建图像,如何操作图像的像素,就需要遍历一幅图像并处理每一个像素.现在我们就来介绍OpenCV三种图像像素的遍历方法: 一. 用cv:: ...

  3. 【C/C++多线程编程之三】创建pthread线程

    多线程编程之创建pthread线程 Pthread是 POSIX threads 的简称,是POSIX的线程标准.           创建线程是多线程编程的第一步,理解线程创建时多线程编程的关键. ...

  4. 多线程编程之三——线程间通讯

    七.线程间通讯 一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信.这种线程间的通信不但是难以避免的,而 ...

  5. 串口编程之三:VMware虚拟机下的串口调试

    Windows为宿主机,VMware虚拟机中安装了 Linux,也可以在 Linux 下编写程序对串口发送消息.原理同样是利用虚拟串口软件VSPM 将COM3与COM4连接后通过串口调试器捕获.下面会 ...

  6. 在C++ GUI Qt中使用QCA进行安全性编程之三

    之前二篇文章介绍了QCA框架的安装和配置,在这篇文章中我将开始编写一个基于数字证书的加密例程,用来讲解QCA框架的使用.其实QCA的应用是非常简单的,且在源代码发行包中也附带了大量的例子(在qca-2 ...

  7. JAVA游戏编程之三----j2me 手机游戏入门开发--俄罗斯方块_5_使用LUA脚本写游戏

    该程序是基于07年底写的一个J2ME小游戏 俄罗斯方块,将全部逻辑绘制都放到LUA脚本里来做,J2ME方面仅作一个绘制库来使用! 建立J2ME程序这里就不说了, 详见我的BLOG http://blo ...

  8. JAVA 并发编程之三:CountDownLatch(门闩)、CyclicBarrier(栅栏)和Semaphore(信号量) 三种并发策略

    在JDK的并发包中已经提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类中提供了一种并发流程控制的手段,Exchanger工具类提供了在 ...

  9. SHELL编程之三剑客

    操作系统三剑客命令 基础知识 正则符号 基础正则符号: ^ 以什么开头的信息进行匹配----- ^oldboy $ 以什么结尾的信息进行匹配----- oldboy​$ ^$ 表示匹配空行信息 . 匹 ...

最新文章

  1. 【转】mssql中大小写的区分
  2. OVS DPDK--数据结构关系(七)
  3. jQuery一些常用特效方法使用实例
  4. LeetCode-二分查找-374. 猜数字大小
  5. c#中实现图像图像卷积与滤波-高斯平滑
  6. 在Virtualbox下为Ubuntu16.04开机自动挂载共享目录的最佳方法
  7. linux 6.5 mongdb php扩展插件,linux下为php添加mongodb扩展
  8. mysql 从后往前截取指定个数字符串_「截取字符串」substring从指定字符串开始截取 - seo实验室...
  9. eslint 快捷键设置_eslint的妙用和快捷修复
  10. 高速钢(HSS)金属切削刀具的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  11. 三菱数据移位指令_三菱FX系列PLC循环与移位类指令的使用方法
  12. teleop app android,使用yocs_cmd_vel_mux进行机器人速度控制切换
  13. vue渲染大量数据如何优化_大数据量场景下的Vue性能优化
  14. Unity3D Content Size Fitter的坑
  15. 蓝桥杯 青少年创意编程大赛 scratch组 (三)
  16. UNITY3D自学(六)-- unity视频播放的Quicktime问题
  17. java常见面试题库大全
  18. 基于改进YOLOv7&OpenCV的行人过马路速度与交通灯实时监测系统(源码&教程)
  19. 华为鸿蒙系统英语报纸_华为鸿蒙系统报名方法
  20. lrtimelapse 5.2.1中文版 附安装教程

热门文章

  1. a标签不跳转的三种方法
  2. 详解音乐唱片的cue文件
  3. 我,是搞IT的~~~
  4. Apollo planning之参考线平滑算法
  5. linux红帽umask,Linux-umask
  6. Java 异常中 e.getMessage() 和 e.toString() e.printStackTrace()的区别常见的几种异常
  7. 记一次赤裸裸的教训:All elements are null
  8. 同城跑腿行业前景可观,该如何搭建生活服务平台
  9. SATA USB 芯片
  10. Java+SpringBoot+VUE电商购物系统(含源码+论文+答辩PPT等)