问题:一种使类成员函数成为Windows 回调函数的方法( 积分:100, 回复:62, 阅读:3393 )分类:Object Pascal ( 版主:menxin, cAkk )来自:savetime, 时间:2004-6-20 2:41:00, ID:2672562 [显示:小字体| 大字体] 一种使类成员函数成为Windows 回调函数的方法savetime2k@ 2004.6.20本文排版格式为:正文由窗口自动换行;所有代码以80 字符为边界;中英文字符以空格符分隔。
未经作者同意请勿在在任何公共媒体转载大富翁satanmonkey 提出一个问题:HOOK 的时候,那个回调函数怎么弄才能做成类的成员?现在回调函数不能是类成员函数,访问不了类的成员变量。
/delphibbs/dispq.asp?lid=2624773后来又在另一篇贴子上也看到类似的问题,看来解决这个问题还有点用(我现在还不知道这有什么用处),所以趁着今天周末思考一下。
(太想睡了,下面只好草率地说明,如有不清楚请提问,或者日后有空再详作解释)一开始我的想法是在类成员的回调函数内部复制参数的值,差不多理顺了,后来发现如果回调函数有返回值时,这种方法不行...只好重新开工,用手工编制机器码的方法完成,其中查询JMP $00001111 这样的立即数跳转机器指令花了一个小时,结果是没有找到,只好以JMP [$00001111] 这个代码代替。
如果有谁知道前一种跳转指令的机器码,请告诉我。
思路是这样的:Windows 回调时跳转到一段自己生成的代码中,这段代码模拟Delphi的成员函数调用。
大概的情况是:在类成员变量中声明一块内存空间TCallbackObject,这块内存中放入一些跳转指令:1.修改ESP;2.放入对象指针;3.跳转到类的成员函数。
typeTCallbackFunc = function(A, B: Integer): Integer; stdcall; // 某回调函数原型// 对象相关的,临时生成的机器码结构TCallbackObject = packed recordCode1: array[1..5] of Byte;SelfPtr: Pointer; // 对象指针Code2: array[1..6] of Byte;FuncPtr: Pointer; // 类成员函数地址end;// 上面TCallbackObject 的结构就是这些汇编代码MOV EAX, [ESP];PUSH EAX;MOV EAX, SelfPtr;MOV [ESP+4], EAX;JMP AbsoluteCallbackAddr;// 一个示范类,其ClassCallback 为相应的Windows 回调函数格式TMyClass = class(TObject)FCallbackObject: TCallbackObject;BaseInt: Integer;constructor Create;procedure MakeCallbackObject;end;// 在自己类的构造函数中生成回调函数–引导机器码constructor TMyClass.Create;beginMakeCallbackObject; // 生成回调函数代码BaseInt := 100; // 示范数据end;// 生成一段回调函数- 引导机器码procedure TMyClass.MakeCallbackObject;constCallbackCode: array[1..SizeOf(TCallbackObject)] of Byte =($8B,$04,$24,$50,$B8,$00,$00,$00,$00,$89,$44,$24,$04,$FF,$25,$00,$00,$00,$00);AbsoluteCallbackAddr: Pointer = @TMyClass.ClassCallback; beginMove(CallbackCode, FCallbackObject, SizeOf(TCallbackObject)); with FCallbackObject dobeginSelfPtr := Self;FuncPtr := @AbsoluteCallbackAddr;end;end;// 示范:在类成员回调函数中使用类成员变量// 注:没测试调用成员函数,应该没什么问题吧beginResult := A + B + BaseInt;end;// 示范:如何使类的回调函数被赋值给Windows 回调函数procedure TForm1.Button1Click(Sender: TObject);varMyClass: TMyClass;CallbackFunc: TCallbackFunc;beginMyClass := TMyClass.Create;CallbackFunc := @MyClass.FCallbackObject;ShowMessage(IntToStr(CallbackFunc(1, 2))); // 模拟Windows 回调ShowMessage(IntToStr(MyClass.ClassCallback(1, 2))); // 对象调用MyClass.Free;end;由于主要的目的是解决Windows 回调函数的兼容问题,所以使用stdcall 调用约定定义类成员函数,如果要在其它情况下使用(我估计不会有其它的情况吧),要修改一些代码。
如何使用上面的代码:* 按Windows 回调函数格式定义类成员函数* 在类中定义一个FCallbackObject 成员变量* 把MakeCallbackObject 函数复制到你的类定义中* 在类的构造函数中运行MakeCallbackObject* 修改AbsoluteCallbackAddr: Pointer = @TMyClass.ClassCallback;指向你的类回调函数* 回调函数的地址设置为FCallbackObject对不住各位,这几天感冒一直没退,头晕脑胀,我要先睡了,我不知道上面的代码有没有错误,有问题多多讨论,多多指教:)来自:刘麻子, 时间:2004-6-20 10:07:52, ID:2672654学习先[:)]来自:sunline, 时间:2004-6-20 10:57:32, ID:2672711跟着学习[:)}来自:satanmonkey, 时间:2004-6-20 11:07:41, ID:2672720学习学习,明天试验一下来自:savetime, 时间:2004-6-20 12:16:25, ID:2672806有问题尽管说,有错误别忘了告诉我:)来自:刘麻子, 时间:2004-6-20 17:47:39, ID:2673157很厉害,老大如何联系啊?我QQ:71892967 , MSN:Liu_mazi@ [:)]来自:savetime, 时间:2004-6-20 18:28:45, ID:2673201呵呵,我是小弟,不是老大。
QQ:7627556 MSN:savetime2k@MSN 不太会用啊,还是用QQ 方便些。
来自:hjb_dydd, 时间:2004-6-20 22:31:47, ID:2673496看了一下,楼主这样也不失为一种比较好的方法,和Borland在Vcl中的MakeObjectInstance过程比起来各有千秋!来自:savetime, 时间:2004-6-21 0:02:15, ID:2673585看到一条绝对地址JMP 指令说明:EA cp ptr16:32 - Jump far, absolute, address given in operand但是不知道机器码EA 后面的cp 值为多少,谁能告诉我。
要实现这个句子:JMP CS:00001111 可不可以?来自:savetime, 时间:2004-6-21 2:08:59, ID:2673617多谢楼上hjb_dydd 一句MakeObjectInstance 的提醒,使我想起可以使用相对地址跳转,不用再考虑远跳转了。
来自:savetime, 时间:2004-6-21 2:26:51, ID:2673618{ 类成员函数转Windows 回调函数通用代码单元}unit ClassCallback;interfacetype TCallbackInstance = array [1..18] of Byte;procedure MakeCallbackInstance(var Instance: TCallbackInstance;ObjectAddr: Pointer; FunctionAddr: Pointer);implementationprocedure MakeCallbackInstance(var Instance: TCallbackInstance;ObjectAddr: Pointer; FunctionAddr: Pointer);const CallbackCode: TCallbackInstance =($8B,$04,$24,$50,$B8,$00,$00,$00,$00,$89,$44,$24,$04,$E9,$00,$00,$00,$00);beginMove(CallbackCode, Instance, SizeOf(TCallbackInstance));PInteger(@Instance[6])^ := Integer(ObjectAddr);PInteger(@Instance[15])^ := Integer(Integer(FunctionAddr) - Integer(@Instance) - 18);end;end.{----------------------------}{ CallbackCode DASM }{----------------------------}{ MOV EAX, [ESP]; }{ PUSH EAX; }{ MOV EAX, ObjectAddr; }{ MOV [ESP+4], EAX; }{ JMP FunctionAddr; }{----------------------------}来自:savetime, 时间:2004-6-21 2:12:52, ID:2673619 这是一个例子:typeTCallbackFunc = function(A, B: Integer): Integer; stdcall; TMyClass = class(TObject)FCallbackInstance: TCallbackInstance;BaseInt: Integer;function ClassCallback(A, B:Integer): Integer; stdcall; end;function TMyClass.ClassCallback(A, B: Integer): Integer; beginResult := A + B + BaseInt;end;procedure TForm1.Button1Click(Sender: TObject);varMyClass: TMyClass;CallbackFunc: TCallbackFunc;beginMyClass := TMyClass.Create;MyClass.BaseInt := 200;MakeCallbackInstance(MyClass.FCallbackInstance, MyClass,@TMyClass.ClassCallback);CallbackFunc := @MyClass.FCallbackInstance;ShowMessage(IntToStr(CallbackFunc(1, 2)));ShowMessage(IntToStr(MyClass.ClassCallback(1, 2)));MyClass.Free;end;来自:Another_eYes, 时间:2004-6-21 11:43:39, ID:2674169解决调用约定转换(stdcall<==>register)再称通用吧, 不然还是特用的. 既然特用, 就没必要搞那么复杂, 完全可以用简单的方法实现.来自:savetime, 时间:2004-6-21 12:05:11, ID:2674227大侠来了,赶紧改标题。