C++ COM编程之QueryInterface函数(一)

前言

组件对外公布的是接口;一个组件可以实现多个接口,也就是说可以对外公布多个接口,之前也总结过了,你很少会100%的去完全了解一个组件的所有接口,就像你去学习编程一样,你几乎不可能去成为编程中的全才。那么,既然我们不能去完全的了解一个组件提供的所有接口,那么我们在实际开发中,如何去判断一个组件是否提供对应的接口呢?看文档?是的,是个好主意,在文档的海洋,找到一个知识点,真的很难,浪费时间和精力;其实,组件本身就提供对自己查询的一个接口,让客户去询问组件,问它是否支持某个接口,在经过多次的这种询问之后,客户对于组件的认识将越来越清晰;而我这篇文章和下一篇文章就是对这种询问机制进行详细的剖析和总结。

关于IUnknown

上面说到组件本身提供一个对自己查询的接口,那么这个接口是什么呢?这就是大名鼎鼎的IUnknown接口了,IUnknown接口在Windows SDK的unknwn.h中定义,它的定义如下:

代码如下:

interface IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_  void **ppvObject) = 0;
    virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
    virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
};

这里的STDMETHODCALLTYPE表示调用方式,也就是windows API的__stdcall方式。可以看到,在IUnknown中定义了一个名为QueryInterface的函数。客户可以调用QueryInterface来判断组件是否支持某个特定的接口,而对于剩下的AddRef和Release两个接口操作,我会在之后的文章中进行总结。

所有的COM接口都需要继承IUnknown接口;因此,如果某个客户拥有一个IUnknown接口的指针,它并不需要知道它所拥有的接口指针到底是指向什么类型的,而只需要知道此接口可以用来查询其它接口就行了。

由于所有的COM接口都首先继承了IUnknown,再根据对之前的文章COM编程——接口的背后 的理解,我们可以知道每个接口的vtbl中的前三个函数都是QueryInterface,AddRef和Release。这就使得所有的COM接口都可以被当成IUnknown接口来处理。如果某个接口的vtbl中的前三个函数不是这三个,那么它将不是一个COM接口。由于所有的接口都是从IUnknown继承而来的,因此所有的接口都支持QueryInterface。所以,组件的任何一个接口都可以被客户用来获取它所支持的其他接口。由于所有的接口指针同时也将是IUnknown指针,客户并不需要单独维护一个代表组件的指针,它所关心的将仅仅是接口的指针。

既然,我们可以只用QueryInterface去询问组件是否支持某个接口,但是,这一切都是基于获得了IUnknown接口指针之后,才能进行的操作,那么如何获得一个指向组件的IUnknown接口指针呢?我们可以实现一个CreateInstance函数,它建立一个组件并返回一个IUnknown指针;对于客户来说,可以调用CreateInstance获得IUnknown指针,而不用使用new操作符了。在系统的总结了COM的所有基础知识之后,我再说说系统提供的一个创建组件实例的API函数。

关于QueryInterface

IUnknown中包含一个名为QueryInterface的成员函数,客户可以通过此函数来查询某个组件是否支持某个特定的接口。若支持,QueryInterface将返回一个指向此接口的指针;否则返回值将是一个错误代码;然后客户可以接着查询其它接口。

从QueryInterface函数的声明中可以看出,QueryInterface有两个参数,第一个参数标识客户所需的接口,这个参数是一个接口标识符(IID)结构,在之后的文章中,我会总结有关IID的知识的;第二个参数用来存放所请求的接口的地址。QueryInterface返回的是一个HRESULT值,它是一个具有特定结构的32位值,之后我也会进行总结的;对于返回的HRESULT值,在实际开发中,需要使用SUCCEEDED宏或FAILED宏去进行判断HRESULT值是表示成功还是失败。

QueryInterface的简单实现

总结了QueryInterface的简单实现,说白了,就是简单工厂模式的实现;上面也说了,就是根据客户提供的IID接口标识符,然后获得对应的接口的指针,返回对应的接口的指针;如果组件支持客户指定的接口,那么应返回S_OK以及相应的指针;若不支持,返回值应是E_NOINTERFACE,并将相应的指针返回值置成NULL。下面通过一个简单的例子来说明QueryInterface的简单实现:

比如有上述的一个结构;接口IX和IY都继承自IUnknown接口,组件CA实现了IX和IY接口,那么QueryInterface的实现应该像下面这样:

代码如下:

HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv)
{
     if (iid == IID_IUnknown)
     {
          *ppv = static_cast<IX *>(this);
     }
     else if (iid == IID_IX)
     {
          *ppv = static_cast<IX *>(this);
     }
     else if (iid == IID_IY)
     {
          *ppv = static_cast<IY *>(this);
     }
     else
     {
          *ppv = NULL;
          return E_NOINTERFACE;
     }
     static_cast<IUnknown *>(*ppv)->AddRef();
     return S_OK;
}

QueryInterface的简单使用

当我获得了一个IUnknown指针以后,就可以调用对应的QueryInterface进行查询了,如下:

代码如下:

void Fod(IUnknown *pI)
{
     IX *pIX = NULL;
     // Ask for interface IX
     HRESULT hr = pI->QueryInterface(IID_IX, (void **)&pIX);
     // Check the return value
     if (SUCCEEDED(hr))
     {
          // Use the interface
          pIX->Fx();
     }   
}

完整的例子

上面说了那么多了,现在提供一个完整的例子,将上面的各种理论知识都在实际代码中进行了实践,让各位能更好的理解QueryInterface。(下载)。

总结

QueryInterface理解起来比较简单,但是,它的理论知识还是必须要去掌握的,理论是一切的基础,没有理论作为支撑,任何实际的操作都不会那么可靠和可信,所以,这篇文章总结的偏于理论多一些。由于QueryInterface部分的内容比较多,使用一篇文章无法总结的齐全,所以,之后我还会继续总结关于QueryInterface的第二部分。

时间: 2014-09-30

C++ COM编程之QueryInterface函数(二)

前言 在COM编程--认识组件中也总结了,COM是一个说明如何建立可动态互变组件的规范,它提供了为保证能够互操作,客户和组件应遵循的一些标准.而在实现和使用QueryInterface时,就需要去遵守一些规则,只有遵守了这些规则,才能是一个正确的COM组件:只有了解了这些规则,才能会真正的了解COM开发. QueryInterface的实现规则 实现QueryInterface需要遵从以下五条规则: 1.QueryInterface总是返回同一IUnknown指针 组件的实例只有一个IUnkno

Linux服务器编程之utime()函数修改文件存取时间

C语言utime()函数:修改文件的存取时间和更改时间 头文件: #include <sys/types.h> #include <utime.h> 定义函数: int utime(const char * filename, struct utimbuf * buf); 函数说明:utime()用来修改参数filename 文件所属的inode 存取时间.结构utimbuf 定义如下: struct utimbuf { time_t actime; time_t modtime;

linux编程之pipe()函数详解

管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,当进程创建管道时,每次都需要提供两个文件描述符来操作管道.其中一个对管道进行写操作,另一个对管道进行读操作.对管道的读写与一般的IO系统函数一致,使用write()函数写入数据,使用read()读出数据. #include<unistd.h> int pipe(int filedes[2]); 返回值:成功,返回0,否则返回-1.参数数组包含pipe使用的两个文件的描述符.fd[0]:读管道,fd[

Python编程之Re模块下的函数介绍

re模块下的函数 compile(pattern):创建模式对象 import re pat=re.compile('A') m=pat.search('CBA') #等价于 re.search('A','CBA') print m <_sre.SRE_Match object at 0x9d690c8> #匹配到了,返回MatchObject(True) m=pat.search('CBD') print m None #没有匹配到,返回None(False) search(pattern,

python 编程之twisted详解及简单实例

python 编程之twisted详解 前言: 我不擅长写socket代码.一是用c写起来比较麻烦,二是自己平时也没有这方面的需求.等到自己真正想了解的时候,才发现自己在这方面确实有需要改进的地方.最近由于项目的原因需要写一些Python代码,才发现在python下面开发socket是一件多么爽的事情. 对于大多数socket来说,用户其实只要关注三个事件就可以了.这分别是创建.删除.和收发数据.python中的twisted库正好可以帮助我们完成这么一个目标,实用起来也不麻烦.下面的代码来自t

python编程之requests在网络请求中添加cookies参数方法详解

哎,好久没有学习爬虫了,现在想要重新拾起来.发现之前学习爬虫有些粗糙,竟然连requests中添加cookies都没有掌握,惭愧.废话不宜多,直接上内容. 我们平时使用requests获取网络内容很简单,几行代码搞定了,例如: import requests res=requests.get("https://cloud.flyme.cn/browser/index.jsp") print res.content 你没有看错,真的只有三行代码.但是简单归简单,问题还是不少的. 首先,这

Linux网络编程之UDP Socket程序示例

在网络传输协议中,TCP协议提供的是一种可靠的,复杂的,面向连接的数据流(SOCK_STREAM)传输服务,它通过三段式握手过程建立连接.TCP有一种"重传确认"机制,即接收端收到数据后要发出一个肯定确认的信号,发送端如果收到接收端肯定确认的信号,就会继续发送其他的数据,如果没有,它就会重新发送. 相对而言,UDP协议则是一种无连接的,不可靠的数据报(SOCK_DGRAM)传输服务.使用UDP套接口不用建立连接,服务端在调用socket()生成一个套接字并调用bind()绑定端口后就可

Android编程之OpenGL绘图技巧总结

本文实例讲述了Android编程之OpenGL绘图技巧.分享给大家供大家参考,具体如下: 很久不用OpenGL ES绘图,怕自己忘记了,于是重新复习一遍,顺便原理性的东西总结如下: 1. Android 3D坐标系统 如图: Android的三维坐标系统中: 坐标原点位于中央, X轴从左向右延伸,原点左边的值为负数,右边为正数: Y轴从下向上延伸,原点下边的值为负数,上边为正数: Z轴屏幕里面向外面延伸,屏幕里面为负数,外面为正数. 2. 开发工具(OpenGL和OpenGL ES)介绍 Ope

Java Web编程之Servlet技术详解

Java Web编程之Servlet技术,知多少? 1.Servlet基础 针对Servlet技术开发,Sun公司提供了一些列接口和类,其中最重要的是javax.servlet.Servlet接口,两个重要的包是javax.servlet和javax.servlet.http,Servlet就是一种实现了Servlet接口的类,它由Web容器(Tomcat/Jetty等)负责调用并创建,用于接收和响应用户请求.Servlet接口中定义了5个抽象方法: Servlet顶层类结构如下所示: 2.第一