熟悉C++开发的朋友们都知道,每一个包含虚函数的类的对象的前四个字节(32位系统中,以下例子都是在32位系统下)的内存中存放着该对象的虚函数表的指针。虚函数表中依次存放着该对象的每个虚函数的地址。
举个例子:
class TestA
{
public:
TestA();
~TestA();
virtual void _cdecl testAFunc();//给该函数加上_cdecl约定是为了保证成员函数的第一个参数this指针可以在该成员函数被调用时压入参数栈
virtual void _cdecl testAFunc1(); //理由同上
};
TestA::TestA()
{
}
TestA::~TestA()
{
}
void TestA::testAFunc()
{
cout<<"testAFunc is called!"<<endl;
}
void TestA::testAFunc1()
{
cout<<"testAFunc1 is called!"<<endl;
}
int main()
{
TestA a;
return 0;
}
对于TestA这个类的对象a来说,它在内存中的布局如下:
我们可以采用取内存的方法来获得某一个虚函数的指针,方法如下:
typedef void(*Func) (void);
在main函数中加入以下代码:
Func pFunc = NULL;
pFunc = (Func)*((int *)(*(int*)(&a)) + 0);
pFunc(); //实际调用的是testAFunc()
pFunc = (Func)*((int *)(*(int*)(&a)) + 1);
pFunc(); //实际调用的是testAFunc1()
输出为:
testAFunc is called!
testAFunc1 is called!
但是通过内存来取虚函数指针的方法有点繁琐,稍微一个不留神忘了指针转换或者解除引用就会导致错误,这里,我给大家介绍一种清晰简单的方法来取虚函数。
如上图所示,虚函数表其实是一块连续的内存,里面每个元素(每四个字节)都是一个函数指针,这样的话,我们完全可以把它看做一个结构体,该结构体的每个成员是一个函数指针。
struct TestA_Vtpr
{
void (*Hook)(TestA *This); //该函数指针的参数列表和返回值必须与类的对应的虚函数的参数列表和返回值一致
void (*Hook1)(TestA *This);//同上
};
如此这样的话,我们完全可以用以下方式来引用TestA 类的虚函数
(将以下代码加入main函数)
TestA a;
TestA_Vtpr **pVtable = (TestA_Vtpr**)(void*)&a;
TestA_Vtpr *pVtpr = *pVtable;
pVtpr->Hook(&a);
pVtpr->Hook1(&a);
(&a)->testAFunc(); //必须用指向a对象的指针来引用testAFunc,否则不会去查询虚表
(&a)->testAFunc1(); //同上
程序输出如下:
testAFunc is called!
testAFunc1 is called!
testAFunc is called!
testAFunc1 is called!
可以看到pVtpr->Hook(&a); 和 (&a)->testAFunc();执行的结果一样的。
亲们,你们明白了么?