2005年03月12日

用Word写的文章为什么在这里发表出来就改变格式了?

Delphi中的回调函数

回调函数
回调(CallBack)函数就是一种两个对象之间的通知方法。被通知者事先约定好发生某种情况时的处理函数,在该情况发生时发起通知者调用该处理函数以完成回调。举例来说,A是一个函数,B是一个定时器对象,A想在指定时间达到时接收到B的通知,执行步骤如下:
//以下为Delphi伪码
TProc = procedure ();//回调函数类型定义,全局函数指针
procedure A();//实际的回调函数

TTimer = class//定时器类
Private
FpA: TProc;
Public
procedure TTimer.SetCallBack(pA: TProc);
procedure Proc();
begin
if Assigned(FpA) then FpA();//约定的时间到了,回调A
end;
end;

//执行过程
Var B: TTimer;
begin

B.SetCallBack(A);//事先约定处理函数A

B.Proc();//某个时刻会发生该方法的调用,则回调A

end;
上文中的TProc是全局函数的定义方式,而全局函数被回调时只能使用全局变量和局部变量,而不能作为对象来调用形如Self.Call的方法,使用起来确实不方便;而且全局函数还会污染命名空间,实在不是个好习惯。如果我们想在B.SetCallBack()中传递一个对象的方法又怎么做呢?实现他并不难,主要是使用TObject.MethodAddress的方法来实现的。把函数A的定义去掉,改为类TA:
TA = class
Published//注意:必须是Published
Procedure CallBack();
End;
//执行过程
Var B: TTimer; A: TA;
begin

B.SetCallBack(A.MethodAddress(‘CallBack’));//事先约定处理函数A.CallBack

B.Proc();//某个时刻会发生该方法的调用,则回调A

end;
这样就可以了。有一点请注意,在声明TA的方法CallBack时,必须声明为Published,否则就会发生错误,这又是为什么呢?原来TObject.MethodAddress方法只能取出Published型的方法,如果没有声明为Published,则A.MethodAddress(‘CallBack’)会返回空,导致错误。另外请注意,使用这种方法是不能将回调函数声明为带有参数的,因为这种方式参数根本无法传递到回调函数中。
有了如上两个缺点,即必须定义为Published类型的方法、不能传递参数,这样的限制也是很大的。还有另外一种定义回调函数的方式,他可以解决这个问题。那就是将TProc的定义改为:
TProc = procedure () of Object;
那么,该回调函数就能够支持任意对象方法了,调用B.SetCallBack时也不用再调用A.MethodAddress(‘CallBack’)来获得回调函数地址了,而是改为B.SetCallBack(A.CallBack)。
如上三种回调方式,各有利弊,需要根据实际需要来决定使用哪一种。

Windows API中的回调函数
在Windows API中有一些函数使用回调函数,他的运行机制与上文提到的Delphi中的回调机制相似。例如CreateThread、SetWindowLong、SetWindowsHook等函数。对应的回调函数可以定义为如下形式:
procedure ThreadFunction(Ptr: Pointer);stdcall;//线程函数
function MessageCallBackFunc(Wnd: HWND; Msg, WParam, LParam: Longint): Longint;stdcall;//窗口消息处理回调函数

回调函数的线程模式
前文提到的回调函数除了线程函数之外,回调者和被回调者都在同一个线程中,这就象管理者将自己的电话告诉了许多工作者,管理者每次都告诉所有工作者该干什么(循环处理),而且一直询问工作者是不是做完某件事了(约定事件发生),直到工作者回答是,工作者打电话(发起回调)给管理者,管理者在不挂电话的过程中将该工作记录在案(回调处理)。
这个步骤实在是罗嗦。管理者会被累垮的,而且也没有效率,过程可以改成这样,管理者在开始时将自己的电话告诉所有工作者,之后他就可以去会见客户或者喝杯茶,当某个工作者完成了工作,给管理者打电话,之后由管理者将该工作记录在案。
假设管理者给工作者的电话没电了或者忘记随身携带,那么事情就麻烦了,所以工作者都处于等待状态。情况还可以改变,那就是管理者告诉工作者邮箱地址,管理者在空闲时处理一下邮件,发现有工作者已经完成了他的工作,他就将该工作记录在案。
如上就是回调函数的三种线程模式:
1、 发起回调者(类比工作者)和被回调者(类比管理者)在同一个线程。
2、 发起回调者和被回调者分属不同的线程,发起回调者在回调时必须等待被回调者处理完成才能返回。
3、 发起回调者和被回调者分属不同的线程,发起回调者在回调时不需要等待被回调者处理完成就可以返回。