这次只是熟悉一下实现回调的整个过程。
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type PFCALLBACK = function(Param1:integer;Param2:integer):integer;stdcall;
// 定义回调函数的类型
var
Form1: TForm1;
gCallBack:PFCALLBACK;
function CBFunc(Param1:integer;Param2:integer):integer;stdcall;
implementation
//回调函数需要定义为全局
{$R *.dfm}
///实现回调函数的功能
function CBFunc(Param1:integer;Param2:integer):integer;
var i:integer;
begin
//messagebox(application.Handle,'回调函数','提示信息',mb_ok);
for i:=0 to 100 do
begin
sleep(100);
self.ListBox1.Items.Add('回调函数');
end;
end;
///
function MyThreadFunc(P:pointer):Longint;stdcall;
begin
gCallBack(0,1);//简单传个参数
end;
procedure testpro ;
var i:integer;
hThread:Thandle;//定义一个句柄
ThreadID:DWord;
begin
for i:=0 to 4 do
begin
messagebox(application.Handle,'123','提示信息',mb_ok);
if (i=2) then
begin
hthread:=CreateThread(nil,0,@MyThreadfunc,nil,0,ThreadID);//利用这种线程怎么说呢,肯定方便啦,但是
//肯定功能上受到好多限制,所以啊,自己写,下次贴个上来
end;
end;
end;
///
function TestCallBack( Func:PFCALLBACK ):integer;
begin
gCallBack:=Func;
testpro;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//testpro;
TestCallBack(@CBFunc);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
self.ListBox1.Clear;
end;
end.
使用回调函数需要注意的地方:
type PFCALLBACK = function(Param1:integer;Param2:integer):integer;stdcall;
// 定义回调函数的类型
function CBFunc(Param1:integer;Param2:integer):integer;stdcall;
//全局函数定义,指向函数的函数,指针!!!名字可以随便取,但参数之类的需要与定义
//的函数类型一致。
function CBFunc(Param1:integer;Param2:integer):integer;
//写该函数体就没什么好说拉
function TestCallBack( Func:PFCALLBACK ):integer;
//传递回调函数的入口地址,最重要啦!
------------------
什么是回调函数?
简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
为什么要使用回调函数?
因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。
如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。
回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。
另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。
不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。
一个简单的回调函数实现
下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。
void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc); |
这两个函数接受以下参数:
·byte * array:指向元素数组的指针(任意类型)。
·int size:数组中元素的个数。
·int elem_size:数组中一个元素的大小,以字节为单位。
·CompareFunction cmpFunc:带有上述原型的指向回调函数的指针。
这两个函数会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):
·-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。
·0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。
·1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。
基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点:
void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc) |
注意:因为实现中使用了memcpy(),所以函数在使用的数据类型方面,会有所局限。
对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串:
int __stdcall CompareInts(const byte* velem1, const byte* velem2) |
下面另有一个程序,用于测试以上所有的代码,它传递了一个有5个元素的数组给Bubblesort()和Quicksort(),同时还传递了一个指向回调函数的指针。
int main(int argc, char* argv[]) |
如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。
调用约定
上面的代码中,可在函数原型中找到__stdcall,因为它以双下划线打头,所以它是一个特定于编译器的扩展,说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32 API(除了个别接受可变参数的除外)都使用它。标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。但在C/C++中,调用约定是调用者负责清理堆栈,而不是被调用函数;为强制函数使用C/C++调用约定,可使用__cdecl。另外,可变参数函数也使用C/C++调用约定。
Windows操作系统采用了标准调用约定(Pascal约定),因为其可减小代码的体积。这点对早期的Windows来说非常重要,因为那时它运行在只有640KB内存的电脑上。
如果你不喜欢__stdcall,还可以使用CALLBACK宏,它定义在windef.h中:
#define CALLBACK __stdcallor |
作为回调函数的C++方法
因为平时很可能会使用到C++编写代码,也许会想到把回调函数写成类中的一个方法,但先来看看以下的代码:
class CCallbackTester |
如果使用微软的编译器,将会得到下面这个编译错误:
error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible |
这是因为非静态成员函数有一个额外的参数:this指针,这将迫使你在成员函数前面加上static。当然,还有几种方法可以解决这个问题,但限于篇幅,就不再论述了。
----------------
//**********************************************************
1 回调函数的概述
回调函数是这样一种机制:调用者在初始化一个对象(这里的对象是泛指,包括OOP中的对象、全局函数等)时,将一些参数传递给对象,同时将一个调用者可以访问的函数地址传递给该对象。这个函数就是调用者和被调用者之间的一种通知约定,当约定的事件发生时,被调用者(一般会包含一个工作线程)就会按照回调函数地址调用该函数。
这种方式,调用者在一个线程,被调用者在另一个线程。
消息:
消息也可以看作是某种形式的回调,因为消息也是在初始化时由调用者向被调用者传递一个句柄和一个消息编号,在约定的事件发生时被调用者向调用者发送消息。
这种方式,调用者在主线程中,被调用者在主线程或者工作线程中。
Delphi事件模型:
在Delphi的VCL中有很多可视化组件都是使用事件模型,例如TForm的OnCreate事件,其原理是:在设计时指定事件函数,在运行时事件触发,则会调用在设计时指定的事件函数。
在机制上,Delphi事件模型与回调是一样的。但具体形式有些区别,纯的回调函数是全局函数的形式,而Delphi事件是对象方法的形式,即可以定义如下回调函数类型
type
TCallBackFunc = procedure (pData: Pointer) of object;
2 回调函数的使用说明
回调函数主要在两个场合使用,第一个是某些windows的API要求用回调函数作为其参数地址,另一种是用户在某种特定的场合定义的某个函数需要使用回调函数作为其参数地址,对于用户的定义的函数来说,一般是当调用动态连接库中的函数时使用。
对于使用一个回调函数主要有以下几个步骤:
1、 定义一个回调函数类型,跟一般的函数过程的定义并没有什么区别,但其定义必须根据需要满足回调函数的函数要求,唯一的区别在于在函数或过程的定义后面必须声明其为windows标准调用;
例:
type
THDFunction= function(I:integer;s:string):integer; stdcall;
对于过程的声明:
type
THDProcedure=procedure(s:string); stdcall;
2、 然后根据此原形定义一个相应的函数或过程,对于这个函数或过程来说名字没有什么要求,对函数其参数的类型和返回值的类型必须和定义的回调函数类型完全一致,对于过程来说,只需要其参数类型一样就可以了。
例:根据上面的函数和过程的原形定义一个相应的函数和一个相应的过程。
函数原形定义:
Function HdFunExample(k:integer,sExam:string):integer; stdcall;
过程定义:
procedure HdProExample(sExam:string);stdcall;
3、 在程序中实现此回调函数或着过程;
Function HdFunExample(k:integer,sExam:string):integer; stdcall;
Begin
End;
procedure HdProExample(sExam:string);stdcall;
begin
end;
4、 调用过程;
回调函数一般作为系统的某个函数的入口地址;
根据调用函数的原形:
假设有如下调用函数:
function DyHdFunExample(HdFun:THDFunction;I:integer):boolean;
注:
在调用函数中通过对函数指针的处理可以直接调用回调函数(即调用函数中的那个是回调函数类型的参数,直接操作它),使回调函数履行一定的操作。即在调用函数中实现回调函数的功能。
调用:
var
I:integer;
begin
I:=DyHdFunExample(@HdFunExample,i);
//…….
End;
//***********************************************************
//把一个方法当作另一个方法的参数, 就是回调方法, 大家习惯称作回调函数
type
TFunType = function(i: Integer): Integer; {声明一个方法类型}
function MyFun(i: Integer): Integer; {建立类型兼容的函数}
begin
Result := i*2;
end;
{把函数当作参数, 再定义一个函数}
function MyTest(x: Integer; F: TFunType): Integer;
begin
Result := F(x);
end;
{测试}
procedure TForm1.Button1Click(Sender: TObject);
var
Fun: TFunType; {声明一个 TFunType 的变量}
i: Integer;
begin
Fun := MyFun; {让方法变量 Fun 指向和它类型兼容的一个方法}
{测试 Fun; Fun 是一个方法变量, 现在去执行那个方法, 它就可以当作那个方法来使用了}
i := Fun(4);
ShowMessage(IntToStr(i)); //8
{把 Fun 当作参数使用; 把函数当作参数使用, 这就是回调函数}
i := MyTest(4,Fun);
ShowMessage(IntToStr(i)); //8
end;