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

【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的内存结构,如图所示:

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

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

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

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

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

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

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

1、接口中函数的数目。

2、接口中函数的顺序。

3、某个函数的参数。

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

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

6、函数可能的返回值。

7、函数返回值的类型。

8、函数参数的含义。

9、接口中函数的含义。

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

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

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

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

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

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

Good  Good  Study,  Day Day Up.

顺序  选择  循环  总结

COM编程之三 QueryInterface的更多相关文章

  1. Java并发编程之三:volatile关键字解析 转载

    目录: <Java并发编程之三:volatile关键字解析 转载> <Synchronized之一:基本使用>   volatile这个关键字可能很多朋友都听说过,或许也都用过 ...

  2. IOS高级编程之三:IOS 多线程编程

    多线程的概念在各个操作系统上都会接触到,windows.Linux.mac os等等这些常用的操作系统,都支持多线程的概念. 当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的. 例如: ...

  3. Qt中的串口编程之三

    QtSerialPort 今天我们来介绍一下QtSerialPort模块的源代码,学习一下该可移植的串口编程库是怎么实现的. 首先,我们下载好了源代码之后,使用QtCreator打开整个工程,可以看到 ...

  4. Javascript异步编程之三Promise: 像堆积木一样组织你的异步流程

    这篇有点长,不过干货挺多,既分析promise的原理,也包含一些最佳实践,亮点在最后:) 还记得上一节讲回调函数的时候,第一件事就提到了异步函数不能用return返回值,其原因就是在return语句执 ...

  5. 5-4 bash脚本编程之三 条件判断及算术运算

    1. 反引号是引用执行结果,并非是返回值 如下是错误的,结果是一行行记录,不是返回值 放大为: 练习 2. shell中如何进行算术运算 A=3 B=4 1. let算术运算表达式 2. $[算术运算 ...

  6. Python socket编程之三:模拟数据库循环发布数据

    1. f1.py # -*- coding: utf-8 -*- import socket import struct import sqlalchemy import pandas ####### ...

  7. Java高效编程之三【类和接口】

    本部分包含的一些指导原则,可以帮助哦我们更好滴利用这些语言元素,以便让设计出来的类更加有用.健壮和灵活. 十二.使类和成员的访问能力最小化 三个关键词访问修饰符:private(私有的=类级别的).未 ...

  8. python socket 编程之三:长连接、短连接以及心跳

    长连接:开启一个socket连接,收发完数据后,不立刻关闭连接,可以多次收发数据包. 短连接:开启一个socket连接,收发完数据后,立刻关闭连接. 心跳:长连接在没有数据通信时,定时发送数据包(心跳 ...

  9. python socket 编程之三:长连接、短连接以及心跳(转药师Aric的文章)

    长连接:开启一个socket连接,收发完数据后,不立刻关闭连接,可以多次收发数据包. 短连接:开启一个socket连接,收发完数据后,立刻关闭连接. 心跳:长连接在没有数据通信时,定时发送数据包(心跳 ...

随机推荐

  1. mac下安装使用svn

    mac自带了svn服务端和客户端,所以只需要简单配置一下就可以使用   转自  http://blog.sina.com.cn/s/blog_677fb16e01011i6l.html 1.创建svn ...

  2. Python-S13作业-day4-之登陆,管理后台

    Python-S13作业-day4-之登陆,管理后台 需求: 本节作业,用户管理程序:          普通用户: 登录,注册,修改密码,查看本用户信息 管理员用户: 查看所有普通用户,按照指定关键 ...

  3. Spring中的工厂模式和单例模式

    Spring预备知识(适合中小型项目) 作用:集成和管理其他框架 工厂模式: A  a  = new A( ); 将类所要创建的对象写入工厂,统一进行管理 package com.spring; pu ...

  4. 如何查看自己的linux是32位还是64位

    查看linux是多少位的几位方法:查看linux机器是32位还是64位的方法:方法一:file /sbin/init 或者 file /bin/ls结果如下:/sbin/init: ELF 64-bi ...

  5. Ninject简单的Demo

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  6. Java遇见HTML——JSP篇之JavaWeb简介

    一.什么是WEB应用程序 Web应用程序是一种可以通过Web(互联网)访问的应用程序.Web应用程序的一个最大好处是用户很容易访问应用程序.用户只需要有浏览器即可,不需要再安装其他软件. 为什么要学习 ...

  7. JavaScript学习笔记之BOM

    BOM的核心对象是window,它既表示浏览器窗口以及页面可见区域,同时也是ECMAScript中的Globe对象,所有的全局变量和函数都是它的属性,并且所有的原声函数以及其他函数也都存在于它的命名空 ...

  8. python gzip,bz2学习

    一.gzip import gzip 1.解压缩 a = gzip.open('a.tar.gz') b = open('a.tar','wb') b.write(a.read()) a.close( ...

  9. [Linux Tips] 1. 查看端口

    查看监听的端口 # netstat -lnp

  10. tcp/ip分片

    from http://blog.csdn.net/cumirror/article/details/5071234 前段时间要做一个关于网络嗅探的程序,里面要重组IP分片,TCP分片. 但做的时候忽 ...