C++的类如果有虚函数,则该类的第一个成员的数值,是一个地址,指向其虚函数表。例如
class CTest
{
public:
virtual void Test1(void)
{
cout<< "CTest Test1\n";
}
virtual void Test2(void)
{
cout<< "CTest Test2\n";
}
private:
int m_nData;
};
在VC里CTest的大小为8,m_nData的偏移地址为4。第一个成员为VTable。
通过虚函数表,把虚函数强制转换为普通函数,可以调用之。例如:
#include<iostream>
using namespace std;
class CTest
{
public:
virtual void Test1(void)
{
cout<< "CTest Test1\n";
}
virtual void Test2(void)
{
cout<< "CTest Test2\n";
}
private:
int m_nData;
};
typedef void (*pFun)(void);
int main(void)
{
CTest cc;
int* pVtblAddr = (int*)*(int*)(&cc); //这是一个地址,指向CTest的虚函数表
int* pFirstVf = (int*)(pVtblAddr[0]); //pVtblAddr相当于一个数组,其中存放的是各个虚函数的地址
int* pSecondVf = (int*)(pVtblAddr[1]);
pFun pf = (pFun)pSecondVf;
pf();
}
运行结果是输出CTest Test2。
现在给Test2加个参数。
#include<iostream>
using namespace std;
class CTest
{
public:
virtual void Test1(void)
{
cout<< "CTest Test1\n";
}
virtual void Test2(int i)
{
cout<< "CTest Test2: i = " << i << "\n";
}
private:
int m_nData;
};
typedef void (*pFun)(int);
int main(void)
{
CTest cc;
int* pVtblAddr = (int*)*(int*)(&cc); //这是一个地址,指向CTest的虚函数表
int* pFirstVf = (int*)(pVtblAddr[0]); //pVtblAddr相当于一个数组,其中存放的是各个虚函数的地址
int* pSecondVf = (int*)(pVtblAddr[1]);
pFun pf = (pFun)pSecondVf;
pf(100);
}
运行时可以正确打印,但是却报错。
原因如下:
调用pf前后,反汇编结果为
pf(100);
004015D9 mov esi,esp
004015DB push 64h
004015DD call dword ptr [ebp-20h]
004015E0 add esp,4
如果在call里F11单步调试,可以看到最终正确调用了CTest::Test2。最后一条反汇编指令为:
virtual void Test2(int i)
{
004016C0 push ebp
004016C1 mov ebp,esp
……………………
004016CB mov dword ptr [ebp-4],ecx
cout<< "CTest Test2: i = " << i << "\n";
……………………
}
……………………
00401703 mov esp,ebp
00401705 pop ebp
00401706 ret 4
在pf(100)那里,调用结束后有个add dsp, 4。而实际调用Test2的时候,最后又有一个ret 4。
所以相当于,压栈时只压入了一个参数100,但却有两次出栈操作,最后造成了栈不平衡了。
根本原因在于,Test2的调用约定是__thiscall,这样的函数在本身被调用结束后退出时会自动
清除栈;而pf的调用约定是_cdecl,main里调用了pf之后,会又有一次清除栈的操作。
解决办法就是修改pf的调用约定。但普通函数是不能用_thiscall声明的,所以只能退而求其次
,把它声明为__stdcall的。
typedef void (__stdcall *pFun)(int);
这样就不会调用出错了。