[CSDN][原创]
无意中试到一种把C++成员函数的地址传给C的方法,能骗过编译器,但是运行时堆栈错误,没太大实际意义,记在这里,权当消遣。
通常情况下C++的成员函数的地址是不能传给C的,除非是静态成员函数。因为普通成员函数有一个隐含的this指针,是C中没有的,如果和C之间互相调用,由于两边参数和堆栈的维护不兼容,会导致堆栈混乱出错。如果传递普通成员函数地址到C中,那么编译器会报错。
例如在C中有一个函数CFun01,需要一个回调函数指针作为参数:
// .c
int CFunc01(int (*cb)(int))
{
return cb(0x55AA);
}
在C++的类中定义一个函数并传给C:
// .cpp
class C
{
public:
int Fun01(int a)
{
printf("C::Fun01: a = 0x%X\n", a);
return 0;
}
int Fun02(int a)
{
CFunc01(&C::Fun01);
return 0;
}
};
在Fun02处编译器会报告错误:
error C2664: 'CFunc01' : cannot convert parameter 1 from 'int (__thiscall C::* )(int)' to 'int (__cdecl *)(int)'
There is no context in which this conversion is possible
通过下面的方法可以骗过编译器,强制将成员函数地址传给C:
// .cpp
#include "stdarg.h"
int GetFuncAddr(int t, ...)
{
va_list va;
va_start(va, t);
int addr = va_arg(va, int);
return addr;
}
extern "C" int CFunc01(int (*)(int));
class C
{
public:
int Fun01(int a)
{
printf("C::Fun01: a = 0x%X\n", a);
return 0;
}
int Fun02(int a)
{
CFunc01((int (*)(int))GetFuncAddr(0, &C::Fun01));
return 0;
}
};
在GetFuncAddr中,利用不定参数的特性,让编译器把函数地址放到堆栈上,然后从堆栈上再把函数地址用整数的形式取出来,这样就绕过了编译器的类型检查,很神奇的感觉。
以上方法在vs2005中测试通过,如果是VC6,把"&C::Fun01"换成"Fun01"即可。
不过虽然绕过了编译器,但是无法通过实际CPU运行,在实际运行中,C::Fun01可以被回调到,但该函数完成返回后堆栈就乱了。
运行结果:
C::Fun01: a = 0x55AA
运行结果是正确的,但运行后有错误提示:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
显然由于函数参数的压栈规则不一致,导致堆栈混乱了。
所以以上方法只能作为一种消遣,最终还是只能使用传统的静态成员的方法,无奈:
传统方法写在下面作为完结。
// .c
int CFunc01(int (*cb)(void *, int), void *context)
{
return cb(context, 0x55AA);
}
// .cpp
extern "C" int CFunc01(int (*)(void *, int));
class C
{
private:
int Fun01(int a)
{
printf("C::Fun01: a = 0x%X\n", a);
return 0;
}
static int __Fun01(void *context, int a);
public:
int Fun02(int a)
{
CFunc01(__Fun01);
return 0;
}
};
int C::__Fun01(void *context, int a)
{
return ((C *)context)->Fun01(a);
}
int _tmain(int argc, _TCHAR* argv[])
{
C c;
return c.Fun02(0);
}