zoukankan      html  css  js  c++  java
  • 关于使用类成员函数作为回调的方法

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

    因为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; 


  • 相关阅读:
    项目管理
    开源视频会议bigbluebutton开发(1)——初始化安装以及配置
    oracle休系统结构
    Tomcat上安装配置Axis
    锁表头
    文件复制三种方法
    程序员技术练级攻略
    Linux (RHEL 5.4)下安装 Oracle 10g R2
    Android 学习资料收集汇总
    WAS61安装调整和应用部署.doc
  • 原文地址:https://www.cnblogs.com/toosuo/p/2315574.html
Copyright © 2011-2022 走看看