为什么类成员函数不能直接做为回调函数?

因为windows中,回调函数都是显式使用CALLBACk修饰符修饰,也就是_stdcall参数传递方式。_stdcall修饰的函数,参数从右至左依次压入堆栈,被调用者负责平衡堆栈。

而所有类的成员函数在定义的时候都被隐式(implicit)定义为__thiscall参数传递方式。__thiscall 修饰的函数参数从右至左依次压入堆栈,被调用者负责平衡堆栈。与所有参数传递方式均不相同的一点:成员函数所在类的this指针被存入ecx寄存器(这个特性只针对Intel x86架构)。

如何让类成员函数成为回调函数

根据第一节对回调函数与类成员函数各自特点的分析。不难发现,只要能想办法在类成员函数被调用之前设置好ecx寄存器,就能在__stdcall调用的基础上模拟出一个完好的__thiscall调用。

想一下,如果我们对象的方法也是一个stdcall调用约定的方法,那么和回调函数还差什么呢?
只差一个参数,第一个参数对象实例的指针,在Delphi,Pascal,Ada中叫Self,C++,java,C#中叫this.VB中叫ME.
那么我们只要塞给它这个对象的地址不就行了吗.好在stdcall约定参数是由右向左传递的,也就是说第一个参数是最后传递的,又由于stdccall约定
参数全部是由栈传递的.所以我们只要把对象指针直接压入栈中就行了.
但别忽略了一点,
call指令相当于
Push 返回地址
Jmp  函数
ret指令相当于
pop  返回地址
Jmp  返回地址
也就是说实际上在调用函数的时候栈顶保留的是返回地址,如果我们直接压入实例指针的话原来,当跳到函数体中,函数会把返回地址当Self,而Self则
会被当成返回地址,具体会有什么样的后果大家自己去想像一下
所以我们做的事情就是弹出返回地址,压入实例地址,压入返回地址,跳到对象方法去执行.
实际上我们就是要构造这样一段代码当回调用,这段代码插入对象实例参数到第一个参数,然后跳到对象方法:
pop    eax            //弹出返回地址到eax
push   对象实例       //压入对象实例
push   eax            //压入返回地址
jmp    对应的对象方法 //跳转到相应的对象方法

具体实现如下

function Cunk(Obj: TObject; CallBackProc: Pointer): Pointer;
const
  PageSize = 4096;
  SizeOfJmpCode = 5;
type
  TCode = packed record
    Int3: Byte;
    PopEAX: Byte;
    Push: Byte;
    AddrOfSelf: TObject;
    PushEAX: Byte;
    Jmp: Byte;
    AddrOfJmp: Cardinal;
  end;
var
  LCode: ^TCode;
begin
  Result := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  LCode := Result;
  LCode^.Int3 := $90; // nop
  // LCode^.Int3 := $CC; //Int 3
  LCode^.PopEAX := $58;
  LCode^.Push := $68;
  LCode^.AddrOfSelf := Obj;
  LCode^.PushEAX := $50;
  LCode^.Jmp := $E9;
  LCode^.AddrOfJmp := DWORD(CallBackProc) - (DWORD(@LCode^.Jmp) + SizeOfJmpCode); // 计算相对地址
end;

procedure Runk(Thunk: Pointer);
begin
  VirtualFree(Thunk, 0, MEM_RELEASE);

end;

转载于:https://www.cnblogs.com/toosuo/archive/2012/01/07/2315574.html

关于使用类成员函数作为回调的方法相关推荐

  1. C++类成员函数作回调函数

    前面写了一篇文章 C语言消息注册派发模式 介绍了下我理解的C语言消息派发.因为C语言是函数式语言,写回调函数的时候很简单 参数就是一个简单的函数指针就好了, 那在C++里的时候 就有些不一样了,虽然C ...

  2. C++中 线程函数为静态函数 及 类成员函数作为回调函数(转载)

    C++中 线程函数为静态函数 及 类成员函数作为回调函数 线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运 ...

  3. C++ 类成员函数指针的使用方法

    C++ 类成员函数指针的使用方法 #include <iostream>void func(){ std::cout << "void func()" &l ...

  4. 将类的成员函数作为回调函数(外一篇:友元函数)

    转自:http://blog.csdn.net/xylary/article/details/1548596 将类成员函数用做C回调函数 提出问题:  回调函数是基于C编程的Windows SDK的技 ...

  5. 如何定义和实现一个类的成员函数为回调函数

    如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过.通过查询资料发现,其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即"this"指针,C++ ...

  6. c++ 线程函数(类成员函数作为线程函数使用)

    C++类成员函数使用时,都会隐式传递一个this指针给该函数,this指针指向该类的对象.函数体可以通过显示调用该指针或直接访问类内成员. 回调函数是通过指针调用的函数,最常使用的回调函数就是在创建线 ...

  7. 如何让API回调你的VC类成员函数而不是静态函数

    首先需要包含一个由yzwykkldczsh同志编写的模板类-----万能多用自适应无限制回调模板(为纪念友人fishskin,此模板又称为H>W模板) /******************** ...

  8. c++类的成员函数作回调函数为啥要声明为static的

    简单说明 C++的类成员函数不能像普通函数那样用于回调,因为每个成员函数都需要有一个对象实例去调用它. 把成员函数作为回调函数,可以把该成员函数声明为静态成员函数,但这样做有一个缺点,就是会破坏类的结 ...

  9. 类成员函数作为CreateThread的回调函数

    类成员函数作为CreateThread的回调函数 0 通过内嵌汇编方式 改变指针的指向 1 头文件 #pragma once#include <Windows.h> class MyThr ...

最新文章

  1. CentOS 7使用systemctl如何补全服务名称
  2. Spring boot使用Rabbitmq注解及消息序列化
  3. JavaScript记录一下
  4. Java解析Json
  5. python无人机路径规划算法_快速拓展随机树(RRT)路径规划,python
  6. HikariCP不断打印WARN日志Failed to validate connection com.mysql.jdbc.JDBC4Connection@xxxxx (...) Possibly
  7. 随机数生成--可复现--可重复:random_state
  8. LINQ的基本语法包含如下的8个上下文关键字,这些关键字和具体的说明如下
  9. 收藏!深度学习必读10篇经典算法论文总结!
  10. C语言k近邻算法及例题,K近邻算法的理解及KD树的构建
  11. 单片机74LS138应用
  12. matlab图像取样和量化,数字图像基础之图像取样和量化
  13. node-gyp rebuild 报错处理
  14. linux 儒略日时间计算,儒略日(儒略日 在线计算器)
  15. Saturday morning
  16. 程序人生 - 西瓜霜能吃下去吗?
  17. 关于使用Restlet的升级
  18. [分享]高仿网易新闻WebApp模板+Dcloud打包源码下载
  19. 简单使用html+css+js随机获取一注双色球号码
  20. access内置函数:(适用access2000)

热门文章

  1. opencv-api warpAffine
  2. numpy random 模块
  3. SQL中GROUP BY的理解
  4. Linux信号signal的介绍和示例
  5. 可靠型园区网组网,用VRRP还是堆叠?
  6. Java基础学习总结(118)——单元测试的必要性和重要性
  7. Maven学习总结(9)——使用Nexus搭建Maven私服
  8. ef mysql 连接数_EF Core 小坑:DbContextPool 会引起数据库连接池连接耗尽
  9. Docker系列(二):Docker安装
  10. 通过注册表修改解锁ExtROM