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来说实在是太重要了。

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Ubuntu Server 18.04 安装图解教程

    Ubuntu Server 18.04 安装图解教程

  • 用js来实现那些数据结构07(链表01-链表的实现)

    前面讲解了数组,栈和队列。其实大家回想一下。它们有很多相似的地方。甚至栈和队列这两种数据结构在js中的实现方式也都是基于数组。无论增删的方式、遵循的原则如何,它们都是有序集合的列表。在js中,我们新建

  • webstorm 2021激活码_通用破解码

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

  • keil c51注册机2032

    keil c51注册机2032keilc51是一款非常优秀的C语言单片机编程软件,同时也是美国KeilSoftware公司出品的51系列软件,是兼容单片机C语言软件开发系统的最新版本,与汇编相比,C语言在功能上、结构性、可读性、可维护性上有明显的优势,因而使得用户易学易用。但是软件是需要收费的,或者是功能上有所限制,因此小编今天为大家带来了keilc51注册码,同样也是注册机,详细的使用教程,以软件安装为例,且期限是到2032年有效,亲测有效,有需要的朋友欢迎下载体验。原文链接:http://www.ddooo.com/soft

  • SAP WebIDE编辑器的主题设置

    SAP WebIDE编辑器的主题设置我的本地Eclipse和sublimeText等编辑器,为了保护视力都设置的是黑色或者豆沙绿的背景,而SAPWebIDEJavaScript编辑器默认的背景色还是纯白色,看久了眼睛很累:这个背景色其实也是可以更换的:WebIDE里选择Preferences把theme改成TommorowNightBlue(dark)即可:要获取更多Jerry的原创文章,请关注公众号”汪子熙”…

    2022年10月17日
  • OpenCV-resize函数「建议收藏」

     OpenCV提供了resize函数来改变图像的大小,函数原型如下:/************************************************************************//*OpenCV图像缩放使用的函数是:resizevoidresize(InputArraysrc,OutputArraydst,Sizedsize,dou…

发表回复

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

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