将类的方法做回调函数的参数 ( 积分: 200 )

  • 主题发起人 主题发起人 blue_morning
  • 开始时间 开始时间
B

blue_morning

Unregistered / Unconfirmed
GUEST, unregistred user!
为什么类的方法不能做为回调函数参数值(本质上都是指针嘛)
因为我要在回调函数中使用Component的方法

我目前是在implementation下面定义一个函数,使用如下的语句
FormName.ComponentName.Method实现对Form或DataModule中Component的引用。
 
为什么类的方法不能做为回调函数参数值(本质上都是指针嘛)
因为我要在回调函数中使用Component的方法

我目前是在implementation下面定义一个函数,使用如下的语句
FormName.ComponentName.Method实现对Form或DataModule中Component的引用。
 
那你把对象做为回调函数参数值就行了,有其他的问题吗?
 
应为类得成员函数(就是你说得类得方法)在进行调用参数传递得时候,会隐藏得传递一个额外得参数-self指针(也就是this指针)
当然,也有办法可以使得类得成员函数成为回调函数(摘自DFW)

思路是这样的:Windows 回调时跳转到一段自己生成的代码中,这段代码模拟 Delphi的成员函数调用。大概的情况是:在类成员变量中声明一块内存空间 TCallbackObject,这块内存中放入一些跳转指令:1.修改 ESP;2.放入对象指针;3.跳转到类的成员函数。

type
TCallbackFunc = function(A, B: Integer): Integer
stdcall
// 某回调函数原型

// 对象相关的,临时生成的机器码结构
TCallbackObject = packed record
Code1: 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;
function ClassCallback(A, B:Integer): Integer
stdcall;
procedure MakeCallbackObject;
end;

// 在自己类的构造函数中生成回调函数 – 引导机器码
constructor TMyClass.Create;
begin
MakeCallbackObject
// 生成回调函数代码
BaseInt := 100
// 示范数据
end;

// 生成一段回调函数 - 引导机器码
procedure TMyClass.MakeCallbackObject;
const
CallbackCode: 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;
begin
Move(CallbackCode, FCallbackObject, SizeOf(TCallbackObject));
with FCallbackObject do
begin
SelfPtr := Self;
FuncPtr := @AbsoluteCallbackAddr;
end;
end;

// 示范:在类成员回调函数中使用类成员变量
// 注:没测试调用成员函数,应该没什么问题吧
function TMyClass.ClassCallback(A, B: Integer): Integer;
begin
Result := A + B + BaseInt;
end;

// 示范:如何使类的回调函数被赋值给 Windows 回调函数
procedure TForm1.Button1Click(Sender: TObject);
var
MyClass: TMyClass;
CallbackFunc: TCallbackFunc;
begin
MyClass := 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
////////////////////////////////////////////////////////////////////////////////////////////////////////////
unit ClassCallback;
interface
type TCallbackInstance = array [1..18] of Byte;
procedure MakeCallbackInstance(var Instance: TCallbackInstance;
ObjectAddr: Pointer
FunctionAddr: Pointer);
implementation
procedure 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);
begin
Move(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
这是一个例子:
type
TCallbackFunc = 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;
begin
Result := A + B + BaseInt;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
MyClass: TMyClass;
CallbackFunc: TCallbackFunc;
begin
MyClass := 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;

 
类的方法都隐含了Self这个参数
用class方法试试
 
不好意思各位。可能是我没有把问题描述清楚。
我说的将类的方法做为回调函数的参数的意思是指:
比如Unit1(Form)引用了Unit2(DataModule)
在Unit2中代码如下:
unit Unit2;

interface

uses
SysUtils, Classes, Dialogs, Forms;

type
//回调函数类型声明
TTestCallBack = procedure;
TDM = class(TDataModule)
private
{ Private declarations }
public
procedure CallMe(Msg: string
CallBackFunction: TTestCallBack = nil);
end;

var
DM: TDM;

implementation

{$R *.dfm}

{ TDataModule2 }

procedure TDM.CallMe(Msg: string
CallBackFunction: TTestCallBack);
begin
ShowMessage(Msg);
if Assigned(CallBackFunction) then
CallBackFunction;
end;

end.

然后在Unit1中有如下的代码
private
procedure CallBack1;
及实现
procedure TMainFrm.CallBack1;
begin
ShowMessage('This is call back function 1');
end;
也就是说CallBack1是MainFrm的私有方法

现在在MainFrm的Button1点击事件中写如下的代码:
procedure TMainFrm.Button1Click(Sender: TObject);
begin
DM.CallMe('1', @CallBack1);
DM.CallMe('1', @Self.CallBack1);
end;
也就是把CallBack1做为回调函数的参数,但提示:[Error] Unit1.pas(70): Variable
required
如果写为:
DM.CallMe('1', Self.MethodAddress('CallBack1'));
不会出错,但不会产生回调操作。

这才是我的问题,实际是我想在私有方法中调用From中的某些控件的方法,否则也可以在implementation下面定义一个函数,使用如下的语句
Form.Component.Method就可以实现对Form或DataModule中Component的引用。

但这样明显多了一个步骤,所以想知道类的方法为什么不能做为回调函数的参数
 
TTestCallBack = procedure;
改为:
TTestCallBack = procedure of object;

另外 MethodAddress 方法只能返回 Published 的方法的地址,对 private 的方法
返回nil.

 
多谢xeen,问题已经搞定。先把分发了。
请再解释一下加上 of object的原因
MethodAddress 是与运行时信息相关的,这个我知道了。
 
of object 表示这个指针指向属于某个类的方法,具体结构是:
TMethod = record
Code, Data: Pointer;
end;
 
后退
顶部