关于类的成员函数作为回调函数的要求

回调函数的调用惯例是stdcall,而类的成员函数的调用惯例是thiscall,故应该是类的静态成员函数作为回调函数才可以,类的普通成员函数不能作为回调函数,因为调用惯例导致的参数不匹配

什么是调用惯例
调用惯例(Calling Conventions)指计算机程序执行时调用函数或过程的一些约定,包括:
函数的参数是通过栈还是寄存器传递?
如果通过栈传递,顺序是怎样的,是从左至右入栈还是相反。
谁负责清理栈,是调用者还是被调用者?
从清理栈的角度来讲,调用惯例可分为三类:函数的调用者清理,函数清理,混合清理(有时由调用者清理,有时由函数自己清理)。

调用者清理
著名的cdecl就是由函数调用者清理栈的调用惯例。 cdecl是基于c语言的调用惯例,也是x86机器上大多数C编译器采用的调用惯例。
函数的返回结果多通过EAX寄存器返回。 对于32位机器,EAX能容纳4个字节。 整数或内存地址(指针),通过EAX寄存器返回是没有问题的。 超过4个字节的结构体呢?如何返回?
对于较小的结构体或对象,可以通过EAX:EDX寄存器对返回。 对于超大的对象或结构体,caller在调用函数之前会分配出内存空间,然后把这个空间地址作为第一个参数隐式地传给函数。被调用的函数callee把结果写进这片内存空间,再pop空间地址,之后才返回。 对于浮点数的结果,似乎是通过 ST0 x87 register(浮点寄存器)返回的。
因为调用者知道为参数分配了多少栈空间,所以由调用者清理栈就有一个好处: 为参数分配的栈空间大小可以动态决定。 因此cdecl支持可变参数的函数的调用,例如printf。
如果强迫某个函数使用cdecl调用惯例,可以在函数声明中加_cdecl关键字,如:

void _cdecl funct();
函数自己清理
pascal,stdcall,fastcall都是由函数来清理栈。 通过阅读程序的汇编代码,可以很容易识别这类调用惯例。因为函数返回前会清理栈。
pascal是基于PASCAL编程语言的函数调用惯例。 参数按照从左到右的顺序压栈(和cdecl的入栈顺序相反)。 OS/21.x,Microsoft Windows 3.x 和 BorlandDelphi1.x中的16位API都使用这种函数调用惯例。
stdcall是从pascal调用惯例演变出来的,和pascal不同的是,stdcall以从右到左的顺序对参数压栈。 返回值存储在EAX寄存器中。Win32 API就是采用的这种调用惯例。
fastcall是混合使用寄存器和栈来存储函数的参数,比如把前两个参数存储在寄存器中,其余的参数入栈。 有Microsoft fastcall和Borland fastcall等不同的实现。
由函数自己清理栈的好处在于:调用者不需要每次调用函数之后都清理栈,从而节省了不少代码, 从而生成的二进制文件比较小。坏处在于,由于清理栈的代码是事先生成在函数体内, 所以不能支持可变参数的函数。

混合清理
混合清理的代表是thiscall,对C++中非静态成员函数使用的就是这种调用惯例。
对于gcc编译器来说,thiscall几乎和cdecl相同:函数调用者负责清理栈,参数按从右到左的顺序入栈。 不同的是,thiscall最后会把this指针压栈,就好象它是函数的第一个参数。(其实也是的吧)
对于Microsoft VC++编译器,thiscall类似于Windows API的stdcall,函数的参数从右到左压栈,由参数来清理栈。和stdcall不同的是,thiscall会通过ECX寄存器来传递this指针。因为由函数自己清理栈不支持可变参数的函数调用,所以对于可变参数的函数,则由函数的调用者来清理栈。这是thiscall的灵活之处。

总结
调用惯例 出栈方 参数传递 名字修饰
cdecl 函数调用方 从右至左的顺序压参数入栈 下划线+函数名
pascal 函数本身 从左至右的顺序入栈 较为复杂,参见pascal文档
stdcall 函数本身 从右至左的顺序压参数入栈 下划线+函数名+@+参数的字节数, 如函数 int func(int a, double b)的修饰名是 _func@12
fastcall 函数本身 头两个 DWORD(4字节)类型或者更少字节的参数 被放入寄存器,其他剩下的参数按从右至左的顺序入栈 @+函数名+@+参数的字节数
thiscall 不一定 从右至左的顺序压参数入栈(有时会通过寄存器传递this指针) 不详

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,成员函数可以作为回调函数使用。这是因为成员函数与普通的函数不同,它们需要通过对象来调用。回调函数通常用来在特定的事件发生时执行某个操作。 当将成员函数作为回调函数使用时,需要使用对象来调用它。首先,我们需要定义一个回调函数的接口,在接口中声明回调函数的参数和返回型。然后,在中定义一个成员函数,该成员函数回调函数的接口相匹配。 接下来,在程序中创建该的对象,并将对象的成员函数作为回调函数传递给需要注册回调函数的地方,通常是在其他或函数中。通过传递对象的地址或引用,其他或函数就可以调用该对象的成员函数作为回调函数。 当事件触发时,那些注册了回调函数的地方就会调用相应的成员函数。由于成员函数是通过对象调用的,它可以访问该对象的成员变量和成员函数,以及其他相关的信息。 通过使用成员函数作为回调函数,可以实现更灵活的程序设计。它可以方便地将代码逻辑封装在中,并在需要的时候进行调用。此外,成员函数作为回调函数还可以在多线程编程中起到重要的作用,可以将任务委托给不同的线程进行执行。 总的来说,C++中可以使用成员函数作为回调函数,通过对象来调用。这种用法可以帮助我们实现更灵活和模块化的程序设计,提高代码的重用性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值