C++通过虚函数表调用虚函数

    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);
这样就不会调用出错了。



猜你喜欢

转载自blog.csdn.net/missgya/article/details/23591059