C++成员函数 this指针 调用约定

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

摘要:介绍了如何取成员函数的地址以及调用该地址.

关键字:C++成员函数this指针调用约定

一、成员函数指针的用法

在C++中,成员函数的指针是个比较特殊的东西。对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用。但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法。C++专门为成员指针准备了三个运算符: "::*"用于指针的声明,而"->*"和".*"用来调用指针指向的函数。比如:

class tt

{

public: void foo(int x){ printf("\n %d \n",x); } };

typedef void ( tt::* FUNCTYPE)(int );

FUNCTYPE ptr = tt::foo; //给一个成员函数指针赋值.

tt a;

(a.*ptr)(5); //调用成员函数指针.

tt *b = new tt;

(b->*ptr)(6); //调用成员函数指针.

注意调用函数指针时括号的用法,因为.* 和->* 的优先级比较低,必须把它们和两边要结合的元素放到一个括号里面,否则通不过编译。不仅如此,更重要的是,无法为成员函数指针进行任何的类型转换,比如你想将一个成员函数的地址保存到一个整数中(就是取类成员函数的地址),按照一般的类型转换方法是办不到的.下面的代码:

DWORD dwFooAddrPtr= 0;

dwFooAddrPtr = (DWORD) &tt::foo; /* Error C2440 */

dwFooAddrPtr = reinterpret_cast (&tt::foo); /* Error C2440 */

你得到只是两个c2440错误而已。当然你也无法将成员函数类型转换为其

它任何稍有不同的类型,简单的说,每个成员函数指针都是一个独有的类型,无

法转换到任何其它类型。即使两个类的定义完全相同也不能在其对应成员函数指针之间做转换。这有点类似于结构体的类型,每个结构体都是唯一的类型,但不同的是,结构体指针的类型是可以强制转换的。有了这些特殊的用法和严格的限制之后,类成员函数的指针实际上是变得没什么用了。这就是我们平常基本看不到代码里有"::*", ".*" 和"->*"的原因。

二、取成员函数的地址

当然,引用某位大师的话:"在windows中,我们总是有办法的"。同样,在C++中,我们也总是有办法的。这个问题,解决办法已经存在了多年,并且广为使用(在MFC中就使用了)。一般有两个方法,一是使用内嵌的汇编语言直接取函数地址,二是使用union类型来逃避C++的类型转换检测。两种方法都是利用了某种机制逃避C++的类型转换检测,为什么C++编译器干脆不直接放开这个限制,一切让程序员自己作主呢?当然是有原因的,因为类成员函数和普通函数还是有区别的,允许转换后,很容易出错,这个在后面会有详细的说明。现在先看看取类成员函数地址的两种方法:

第一种方法:

template

void GetMemberFuncAddr_VC6(ToType& addr,FromType f)

{

union

{

FromType _f;

ToType _t;

}ut;

ut._f = f;

addr = ut._t;

}

这样使用:

DWORD dwAddrPtr;

GetMemberFuncAddr_VC6(dwAddrPtr,&tt::foo);

为什么使用模版? 呵呵,如果不使用模版,第二个参数该怎么些,写成函数指针且不说太繁琐,关键是没有通用性,每个成员函数都要单独写一个转换函数。

第二种方法:

#define GetMemberFuncAddr_VC8(FuncAddr,FuncType)\

{ \ __asm \

{ \

mov eax,offset FuncType \ }; \

__asm \

{ \

mov FuncAddr, eax \ }; \

}

这样使用:

DWORD dwAddrPtr;

GetMemberFuncAddr_VC8(dwAddrPtr,&tt::foo);

本来是想写成一个模版函数的,可惜虽然通过了编译,却不能正确运行。估计在汇编代码中使用模版参数不太管用,用offset取偏移量直接就得0。

上面的宏是可以正确运行的,并且还有一个额外的好处,就是可以直接取私有成员函数的地址(大概在asm括号中,编译器不再检查代码的可访问性)。不过缺点是它在vc6下是无法通过编译的,只能在VC8下使用。

三、调用成员函数地址

通过上面两个方法,我们可以取到成员函数的地址。不过,如果不能通过地址来调用成员函数的话,那也还是没有任何用处。当然,这是可行的。不过在这之前,需要了解关于成员函数的一些知识。

我们知道,成员函数和普通函数最大的区别就是成员函数包含一个隐藏的参数this指针,用来表明成员函数当前作用在那一个对象实例上。根据调用约定(Calling Convention)的不同,成员函数实现this指针的方式也不同。如果使用__thiscall调用约定,那么this指针保存在寄存器ECX中,VC编译器缺省情况下就是这样的。如果是__stdcall或__cdecl调用约定,this指针将通过栈进行

传递,且this指针是最后一个被压入栈的参数,相当于编译器在函数的参数列

表中最左边增加了一个this参数。

这里还有件事不得不提,虽然vc将__thiscall类型作为成员函数的默认类型,但是vc6却没有定义__thiscall关键字!如果你使用__thiscall来定义一个函数,编译器报错:'__thiscall' keyword reserved for future use。

知道这些就好办了,我们只要根据不同的调用约定,准备好this指针,然后象

普通函数指针一样的使用成员函数地址就可以了。

对__thiscall类型的成员函数(注意,这个是VC的默认类型),我们在调用之前加一句: mov ecx, this; 然后就可以调用成员函数指针。例如:

class tt

{

public:

void foo(int x,char c,char *s)//没有指定类型,默认是

__thiscall.

{

printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);

}

int m_a;

};

typedef void (__stdcall *FUNCTYPE)(int x,char c,char *s);//

定义对应的非成员函数指针类型,注意指定__stdcall.

tt abc;

abc.m_a = 123;

DWORD ptr;

DWORD This = (DWORD)&abc;

GetMemberFuncAddr_VC6(ptr,tt::foo); //取成员函数地址.

FUNCTYPE fnFooPtr = (FUNCTYPE) ptr;//将函数地址转化为普通函数的指针.

相关文档
最新文档