类之间强制转换后虚函数的调用问题,不知道怎么描述,进来细看

Damn_boy 2011-12-28 02:52:42
INonDelegatingUnknown接口与IUnknow接口
定义基本类似 定义如下

interface IUnknown
{
virtual HRESULT QueryInterface(REFIID iid,void **ppv);
virtual ULONG AddRef(void);
virtual ULONG Release(void);
};

interface INonDelegatingUnknown
{
virtual HRESULT NonDelegatingQueryInterface (REFIID riid, LPVOID *ppv) ;
virtual ULONG NonDelegatingAddRef(void) ;
virtual ULONG NonDelegatingRelease(void) ;
};

这样两个函数定义都很相似的两个类可以不可以理解成:
虚函数定义完全相同的两个类,其对象的虚函数表结构也完全相同?

然后问题来了
我定义了这样两个类

class A
{
public:
virtual void method1()
{
cout << "This is A`s method1()"<<endl;
}
virtual void method2()
{
cout << "This is A`s method2()"<<endl;
}
virtual void method3()
{
cout << "This is A`s method3()"<<endl;
}
};

class CallA
{
public:
virtual void callMethod1() = 0;
virtual void callMethod2() = 0;
virtual void callMethod3() = 0;
};


现在我通过实例化一个A 并且创建一个CallA指针
通过强制转换 将A转化为一个CallA指针


A testA;
CallA *pCallA = (CallA *)(&testA);


然后我通过pCallA调用自身并不存在的方法。可以打到调用A中已经实现好的方法的目的

pCallA->callMethod1();


请问为什么会有这种使用方法?应用在什么场合?为什么要这样用。

我实在看和COM有关的文档里看到这样一句代码联想到的这个

CMyComponent::CMyComponent(IUnknown *pOuterUnkown)
{
if (pOuterUnknown == NULL)
m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;
else
m_pUnknown = pOuterUnknown;

[ ... more constructor code ... ]
}


红色部分
CMyComponent继承了INonDelegatingUnknown 接口
将this指针通过两级转换 使this指针先指向CMyComponent内的INonDelegatingUnknown 部分
再转换为IUnknown类型。
具体调用的时候
通过m_pUnknown -> QueryInterface()
可能调用到INonDelegatingUnknown内的NonDelegatingQueryInterface方法
也可能调用到IUnknown内的QueryInterface方法


...全文
166 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
qscool1987 2011-12-28
  • 打赏
  • 举报
回复
这种问题真是蛋疼啦
在栈结构上
CallA *pCallA = (CallA *)(&testA);
这个语句就是个模型替换,这个替换分扩充替换和切割替换,决定于替换对象模型与被替换模型的大小
这种替换没有意义,替换后,内存空间里还残留着被替换对象的数据值,数据按字节以及声明次序依次占据空间。结果导致CallA 类的vptr的值是原来对象testA的vptr的值,这个指针指向的是A类虚表地址,更要命的是:编译器寻址虚函数是很笨的,在编译的时候只记住了每个虚函数的索引,比如说callMethod3();他只会机械的去索引为2的位置,然后把函数解析出来。
这个时候调用虚函数

|-------|<<--pCallA A类的虚函数表
|vptr --| ------->> |------|
|data1 | 0| vf1 |
|data2 | 1| vf2 |
|data3 | 2| vf3 |
|.... | |------|
|-------|
|

对于这种转型在没有数据成员的类之间还是可以的,对于有数据的类之间,这种替换着实没有意义~~
Enter空格 2011-12-28
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 mymixing 的回复:]
只因为CallA *pCallA = (CallA *)(&testA);
这句话过后CallA类型下的虚函数表指针,指向了A的虚函数表。
所以当你调用你的第一个到第三个CallA的虚函数时,跳转的函数执行地址会分别对应于
A虚函数表中前3个以4字节划分的地址(64位的就是8字节划分)。
[/Quote]

纠正一下

只因为CallA *pCallA = (CallA *)(&testA);
这句话过后pCallA实例下的虚函数表指针,指向了A的虚函数表。
所以当你调用你的第一个到第三个CallA的虚函数时,跳转的函数执行地址会分别对应于
A虚函数表中前3个以4字节划分的地址(64位的就是8字节划分)。
Enter空格 2011-12-28
  • 打赏
  • 举报
回复
只因为CallA *pCallA = (CallA *)(&testA);
这句话过后CallA类型下的虚函数表指针,指向了A的虚函数表。
所以当你调用你的第一个到第三个CallA的虚函数时,跳转的函数执行地址会分别对应于
A虚函数表中前3个以4字节划分的地址(64位的就是8字节划分)。
孤舟 2011-12-28
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 wsxqaz 的回复:]
这个我没有是试过,不过我分析dll里虚表记录方法偏移量(虚地址+偏移就是真正的方法地址),以前我们将虚表删除,防止别人调用直接通过编译量调用方法,而你两个类里,方法类型和参数都是一样的,也就是说偏移地址可能相同,所以才会产生你的问题瓦?

具体还请高手讨论
[/Quote]
你两个类里,方法类型和参数都是一样的,也就是说偏移地址可能相同,所以才会产生你的问题瓦?

已经说到点了~~就是因为偏移地址相同造成的 ~至于细说 太麻烦了 纯粹蛋疼
wsxqaz 2011-12-28
  • 打赏
  • 举报
回复
这个我没有是试过,不过我分析dll里虚表记录方法偏移量(虚地址+偏移就是真正的方法地址),以前我们将虚表删除,防止别人调用直接通过编译量调用方法,而你两个类里,方法类型和参数都是一样的,也就是说偏移地址可能相同,所以才会产生你的问题瓦?

具体还请高手讨论
孤舟 2011-12-28
  • 打赏
  • 举报
回复
首先 你这么强制转换是错误的 类之间的强制转换只保证将子类的指针强制转换成基类 时的正确性
其他所有的自定义类之间的强制转换都不保证其正确性 行为视编译器而定
规则就在那里 为啥都喜欢违反规则呢?
另外你上面的问题建议去仔细的看下 虚函数表的实现
有2本书里有详细说明 深度探索C++面向对象模型 和 C++设计与演化
qscool1987 2011-12-28
  • 打赏
  • 举报
回复
1.虚函数表是属于类的,每个类都有一个虚函数表
只要是不同的类,哪怕类里面的虚函数标志完全相同,也是属于不同的虚函数表,编译后被解析为不同类的虚函数,参考深度探索C++对象模型 函数语意学
2.指针转型只是影响 被指出的内存的大小和其内容
CallA *pCallA = (CallA *)(&testA);
拿你这句来说,CallA *pCallA 编译时期就确定了要指出的内存大小(内容不确定)
(CallA *)(&testA); 这个转型的作用是改变&testA这个指针所指出的内存大小,改为pCallA需要指出的内存大小
正个语句的意思就是,将首地址赋给pCallA ,在&testA指针指出的内存上扩充或者切割,以适应pCallA 所需要指出的内存大小
事实上pCallA->callMethod1();
这种调用没有定义

64,647

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧