QueryInterface的本质初探

QueryInterface的本质初探转载请注明出处,版权归作者所有 lyzaily@126.comyanzhong.lee  本文写给COM的初学者!QueryInterface接口对COM的重要性不言而喻,该接口的实现有个规则——由QueryInterface返回的IUnknow接口指针必须相同,我的疑问是微软是如何使用C++实现这一COM规则的呢?请读者注意,我只探讨微软使用C++

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

 转载请注明出处,版权归作者所有

 lyzaily@126.com

yanzhong.lee

 

 本文写给COM的初学者!

QueryInterface接口对COM的重要性不言而喻,该接口的实现有个规则——由QueryInterface返回的IUnknow接口指针必须相同,我的疑问是微软是如何使用C++实现这一COM规则的呢?请读者注意,我只探讨微软使用C++实现的COM,而不是其他公司使用其他语言实现的COM组件,当然无论什么公司使用十分语言,都必须遵循COM给出的规则。

为了实现该接口,COM给出了5个规则,具体的可以参考《COM技术内幕》一书。

 

为了得到该问题的答应,我们首先要分析一下C++类中的虚函数,以及这些虚函数如何在子类中被继承的。

最有说服力的莫过于真实的例子了,我还是给出在VS2005中写的例子吧,我先给出一个例子, 就回答了我上面提出的问题。

 

//给出一个纯虚基类

class CBase{

public:
 virtual int func(int param) = 0;
private:
   
};

//给出基类的第一个子类

class CTestA:public CBase{

public:
 CTestA();
 ~CTestA();
 virtual int func(int param);
private:
 int m_value;
};
int CTestA::func(int param)
{

 m_value += param;
 this;    //为了查看this值而添加
 return 0;
}
CTestA::CTestA()
{

 m_value = 0;
}
CTestA::~CTestA()
{

}

 

//给出基类的第二个子类

class CTestB:public CBase{

public:
 CTestB();
 ~CTestB();
 virtual int func(int param);
private:
 int m_valueb;
};

int CTestB::func(int param)
{

 m_valueb += param;
 this; //为了查看this值而添加
 return 0;
}
CTestB::CTestB()
{

 m_valueb = 0;
}
CTestB::~CTestB()
{

}

 

//给出CTestA和CTestB的公共子类
class CTestSub:public CTestA,public CTestB
{

 public:
 virtual int func(int param);
private:
};

int CTestSub::func(int param)
{

 void *temPtr = NULL;
 temPtr = static_cast<CTestA*>(this);
 temPtr = static_cast<CTestB*>(this);
 return 0;
}

//测试主函数

int _tmain(int argc, _TCHAR* argv[])
{

  CTestSub sub;
 CBase*  ptr =  static_cast<CTestA*>(&sub);  //(1)
 CTestA* aPtr = static_cast<CTestA*>(&sub); //(2)
 CTestB* bPtr = static_cast<CTestB*>(&sub); //(3)
    
 ptr->func(1);  //(4)
 aPtr->func(2); //(5)
 bPtr->func(3); //(6)

 return 0;
}
如果在页节点的子类中(如:CTestSub 类)实现了基类(如:CBase类)中声明的虚函数,则在(4)-(6)的函数调用中,我们进入的是叶节点类(CTestSub类)实现的func函数,而没有调用其父类CTestA或CTestB类中的func函数,这一点符合C++的规则的——C++中的规则是这样的,如果某个方法在类中被声明为virtual的,并在子类中已经重新实现了,我们在用指向父类的指针(该指针被赋值成子类对象的地址)调用该虚函数时,调用的是子类中实现的函数,这个子类不是其他的子类,而是其地址被赋给了父类指针的子类(这里就是sub对象对应的类);这种现象出现的原因是——子类中维护的虚函数表中有关func函数的地址已经被替换成子类中实现的func函数地址,所以真正调用的是CTestSub类实现的func函数;如果CTestSub不重新实现func函数,那么CTestSub虚函数表中的func处的地址仍然是父类中func的地址,在这样的情况下真正被调用的函数体当然是父类中实现的func函数了,如果调用的是父类中的函数func,那么func中使用的this指针当然是指向父类的实例了,这点规则和我们实验的结果一致的。

在CTestSub类中实现func时得出如下试验结果:

在该测试程序中我们三次进入func函数,其中的this值都是一样的为:0x12ff48,这个也是对象sub的地址。

 

 

那么如果我们没有实现CTestSub 类中的func函数时,会有什么结果呢?????这点疑问非常关键!

经过测试,如果不实现CTestSub 中的func函数,这上面(4)-(6)调用函数时,进入的是不同函数而且this指针也不一样,(4)和(5)进入的是CTestA实现的func函数,this指针指向的是sub对象中包含的CTestA对象的内存地址;(6)进入的是CTestB实现的func函数,this指针指向的是sub对象中包含的CTestB对象的内存地址。

这个实验的事实,说明了每个类对象都维护一个虚函数表(vtbl)CTestA、CTestB以及CTestSub 维护的是不同的vtbl,每个类按类声明时虚函数的顺序将本类中实现的虚函数指针填写到自己的虚表中,所以如果CTestSub 中重新实现了CTestA或CTestB中实现的虚函数,则在子类的虚函数表中将用子类实现的虚函数地址来覆盖父类中实现的函数地址;如果子类没有实现父类中实现的虚函数,则虚表中填充的仍然是父类中实现的虚函数地址;所有如果CTestSub 中不实现虚函数func,则CTestSub的实例sub中的虚表中保存的还是父类中的func函数地址,因此最终调用的就是CTestA或CTestB的func函数了。

讲到这里,大家对QueryInterface为什么会返回相同的IUnkown接口指针有所了解了吧!

这个问题的回答归总如下:

实现组件的类实现了IUnkown中的虚函数QueryInterface,这一点保证实现组件的类维护的虚表VTBL中存储的是该类中实现的QueryInterface函数地址,而不是父类中的QueryInterface函数地址。这样在QueryInterface使用的this指针就是组件的类的实例地址,而不是组件父类的实例地址了。要是的返回的IUnknow地址一致,则this指针指向组件类的实例是必需的。

 其实这篇文章名字也可以称为《QueryInterface中使用的this指针究竟指向谁?》。呵呵,当然指向组件类的实例啊!从上面的例子可以知道,调用func时,如果真正调用的是父类的func实现,这时func中使用的this指针就是指向sub中的父类实例空间;如果真正调用的是CTestSub类实现的func函数,则此时func中使用的this指针就是指向CTestSub 的实例sub。COM的QueryInterface函数也就是使用了C++虚函数的这一技术规则;只要COM组件类实现了QueryInterface接口函数,无论怎么调用该函数,该函数中使用的this指针始终指向组件类的实例。总之一句话,调用哪个类实现的func函数,那么func中使用的this指针就指向哪个类的实例。

以上解释可以能比较啰嗦,但是看官要定下神来慢慢缕缕就能理解QueryInterface的本质了,要通晓COM本质,这点必须弄明白,因为QueryInterface对COM来说实在是太重要了。

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

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/162932.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 安捷伦示波器使用说明书_安捷伦信号发生器使用方法

    安捷伦示波器使用说明书_安捷伦信号发生器使用方法本帖最后由god_blessme于2017-9-1913:45编辑小弟最近在搞一个程序,是要读取安捷伦示波器每一屏数据并储存,网上貌似对于tek示波器连接的比较多,安捷伦的超级少,所以大部分是自己看着改的命令,现在碰到的问题很奇葩,运行程序后一个figure显示的数据是正确的,一个figure显示的是错误的。然而我在循环里把waveform_YIncrement变量的注释去掉的话,fig…

    2022年10月12日
    0
  • 发挥人的主观能动性必须的前提是_发挥人主观能动性的基本途径是

    发挥人的主观能动性必须的前提是_发挥人主观能动性的基本途径是曾任百度研发工程师。2015年加入多点在线科技有限公司,任大数据团队负责人、高级架构师。负责研发了夜神App推荐系统、多点BI平台、AiAdmobi广告平台、RTB投放系统、CTR预估及用户画像等。与阿里云的各种大数据服务深度结合,熟悉Maxcompute、机器学习、推荐系统等服务。也熟悉基于EMR上开源工具的使用,包括Spark,Yarn,…

    2025年7月9日
    0
  • webstorm激活教程破解方法

    webstorm激活教程破解方法,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月15日
    65
  • PLC编程从入门到精通视频教程【副业学习会】

    PLC编程从入门到精通视频教程【副业学习会】PLC编程视频教程共73课,从入门到精通。从基础讲起,一步步提高PLC编程技巧。本套教程分为:电工基础教程、PLC入门教程、PLC高级教程、PLC经验与技巧、触摸屏(人机)编程教学。此视频通俗易懂,而且很实用。![在这里插入图片描述](https://img-blog.csdnimg.cn/20210715203622364.png)课程目录:第1章电工基础教程01电工基础的简介.mp402工厂用电.mp403看懂基本电路.mp404自锁、正反转电路.m…

    2022年9月4日
    2
  • oracle修改用户密码语句_oracle查询密码修改记录

    oracle修改用户密码语句_oracle查询密码修改记录GPS平台、网站建设、软件开发、系统运维,找森大网络科技!https://cnsendnet.taobao.com来自森大科技官方博客http://www.cnsendblog.com/index.php/?p=1596SYS用户是Oracle中权限最高的用户,而SYSTEM是一个用于数据库管理的用户。在数据库安装完之后,应立即修改SYS,SYSTEM这两个用户的密码,以保证数据库的安全。安装完之后修改密码方法cmd命令行下输入sqlplus/assysdba;法1.SQL>al

    2022年7月28日
    1
  • 最新手机号段归属地数据库 (2021年4月版) 473101行

    最新手机号段归属地数据库 (2021年4月版) 473101行最新手机号段归属地数据库(2021年4月发行版)473101行基于:最新手机号段归属地数据库名称:手机号码归属地查询dat高效率查询压缩:原版txt为25M,生成这种dat结构为2.86M性能:每秒解析300w+,简洁高效创建:qqzeng-ip开发参考手机归属地查询c#javaphp解析dat内存优化版快速内存数据库Redis版以及导入数据库mys…

    2022年7月22日
    15

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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