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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 五大常用算法之一:分治算法

    一、基本概念在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求

    2021年12月25日
    38
  • 77. Combinations「建议收藏」

    77. Combinations「建议收藏」Giventwointegers n and k,returnallpossiblecombinationsof k numbersoutof1… n.Forexample,If n =4and k =2,asolutionis:[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],]

    2022年10月6日
    2
  • linux安装PyCharm,实用功能!!!!!!「建议收藏」

    linux安装PyCharm,实用功能!!!!!!「建议收藏」linux安装PyCharm 1.下载http://www.jetbrains.com/pycharm/download/2.安装PyCharm$cdDownloads/$tarxfzpycharm-*.tar.gz$rm pycharm-*.tar.gz$cdpycharm-community-3.4.1/bin/$./pycharm.sh安…

    2022年8月25日
    6
  • pushd popd命令「建议收藏」

    pushd popd命令「建议收藏」在本系列的第一部分中,我们通过讨论 cd- 命令的用法,重点介绍了Linux中的命令行导航。还讨论了一些其他相关要点/概念。现在进一步讨论,在本文中,我们将讨论如何使用 pushd 和 popd 命令在Linux命令行上获得更快的导航体验。在我们开始之前,值得说明的一点是,此后提到的所有指导和命令已经在Ubuntu14.04和Bashshell(4.3.11)上

    2022年6月28日
    37
  • 简述数字证书的生成过程(数字证书认证过程)

    https://blog.csdn.net/abinge317/article/details/51791856RSA非对称加密的2个用途:加密(防窃听)RSA非对称加密会用到一对密钥,分别称为公钥和私钥,公钥加密之后的数据可以通过私钥来进行解密,私钥加密的数据也同样可以用对应的公钥进行解密。在web数据传输过程中,由于客户端和服务器端是多对一的关系,因此可以让所有的客户端持有相同的公钥,服务器持…

    2022年4月12日
    362
  • Jenkins详细教程

    Jenkins详细教程大纲  1.背景  在实际开发中,我们经常要一边开发一边测试,当然这里说的测试并不是程序员对自己代码的单元测试,而是同组程序员将代码提交后,由测试人员测试;  或者前后端分离后,经常会修改接口,然后重新部署;  这些情况都会涉及到频繁的打包部署;  手动打包常规步骤:  1.提交代码  2.问一下同组小伙伴有没有要提交的代码  3.拉取代码并打包(war包,或者jar包)  4.上传到Linux服务器  5.查看当前程序是否在运行  6.关闭当前程序  .

    2022年5月15日
    65

发表回复

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

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