回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在windows编程使用的场合很多,比如hook回调函数:mouseproc,getmsgproc以及enumwindows,drawstate的回调函数等等,还有很多系统级的回调过程。本文不准备介绍这些函数和过程,而是谈谈实现自己的回调函数的一些经验。
之所以产生使用回调函数这个想法,是因为现在使用vc和delphi混合编程,用vc写的一个dll程序进行一些时间比较长的异步工作,工作完成之后,需要通知使用dll的应用程序:某些事件已经完成,请处理事件的后续部分。开始想过使用同步对象,文件影射,消息等实现dll函数到应用程序的通知,后来突然想到可不可以在应用程序端先写一个函数,等需要处理后续事宜的时候,在dll里直接调用这个函数即可。
于是就动手,写了个回调函数的原形。在vc和 delphi里都进行了测试
一:声明回调函数类型。
vc 版 typedef int (winapi *pfcallback)(int param1,int param2) ;
delph版 pfcallback = function(param1:integer;param2:integer):integer;stdcall;
实际上是声明了一个返回值为int,传入参数为两个int的指向函数的指针。
由于c++和pascal编译器对参数入栈和函数返回的处理有可能不一致,把函数类型用winapi(winapi宏展开就是__stdcall)或stdcall统一修饰。
二:声明回调函数原形
声明函数原形
vc 版 int winapi cbfunc(int param1,int param2);
delphi 版 function cbfunc(param1,param2:integer):integer;stdcall;
以上函数为全局函数,如果要使用一个类里的函数作为回调函数原形,把该类函数声明为静态函数即可。
三: 回调函数调用调用者
调用回调函数的函数我把它放到了dll里,这是一个很简单的vc生成的win32 dll.并使用def文件输出其函数名 testcallback。实现如下:
pfcallback gcallback=0;
void winapi testcallback(pfcallback func)
{
if(func==null)return;
gcallback=func;
dword threadid=0;
handle hthread = createthread(
null,
null,
thread1,
lpvoid(0),
&threadid
);
return;
}
此函数的工作把传入的 pfcallback func参数保存起来等待使用,并且启动一个线程。声明了一个函数指针pfcallback gcallback保存传入的函数地址。
四:回调函数如何被使用:
testcallback函数被调用后,启动了一个线程,作为演示,线程人为的进行了延时处理,并且把线程运行的过程打印在屏幕上.
本段线程的代码也在dll工程里实现
ulong winapi thread1(lpvoid param)
{
tchar buffer[256];
hdc hdc = getdc(hwnd_desktop);
int step=1;
msg msg;
dword starttick;
//一个延时循环
for(;step
五:万事具备
使用vc和delphi各建立了一个工程,编写回调函数的实现部分
vc 版
int winapi cbfunc(int param1,int param2)
{
int res= param1+param2;
tchar buffer[256]="";
sprintf(buffer,"callback result = %d",res);
messagebox(null,buffer,"testing",mb_ok); //演示回调函数被调用
return res;
}
delphi版
function cbfunc(param1,param2:integer):integer;
begin
result:= param1+param2;
tform1.edit1.text:=inttostr(result); / /演示回调函数被调用
end;
使用静态连接的方法连接dll里的出口函数 testcallback,在工程里添加 button( 对于delphi的工程,还需要在form1上放一个edit控件,默认名为edit1)。
响应buttonclick事件调用 testcallback
testcallback(cbfunc) //函数的参数cbfunc为回调函数的地址
函数调用创建线程后立刻返回,应用程序可以同时干别的事情去了。现在可以看到屏幕上不停的显示字符串,表示dll里创建的线程运行正常。一会之后,线程延时部分结束结束,vc的应用程序弹出messagebox,表示回调函数被调用并显示根据param1,param2运算的结果,delphi的程序edit控件里的文本则被改写成param1,param2 的运算结果。
可见使用回调函数的编程模式,可以根据不同的需求传递不同的回调函数地址,或者定义各种回调函数的原形,实现多种回调事件处理,可以使程序的控制灵活多变,也是一种高效率的,清晰的程序模块之间的耦合方式。在一些异步或复杂的程序系统里尤其有用 — 你可以在一个模块里专心实现模块核心的业务流程和技术功能,外围的扩展的功能只给出一个回调函数的接口,通过调用其他模块传递过来的回调函数地址的方式,将后续处理无缝地交给另一个模块,随它按自定义的方式处理。
本文的例子使用了在dll里的多线程延时后调用回调函数的方式,只是为了突出一下回调函数的效果,其实只要是在本进程之内,都可以随你高兴可以把函数地址传递来传递去,当成回调函数使用。
这样的编程模式原理非常简单单一:就是把函数也看成一个指针一个地址来调用,没有什么别的复杂的东西,仅仅是编程里的一个小技巧。至于回调函数模式究竟能为你带来多少好处,就看你是否使用,如何使用这种编程模式了。
//———————————————————————-
又一篇关于回调函数的文章
回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就
是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可
以在回调函数里完成你要做的事。
capvideostreamcallback 这个回调函数,我没有做过,看了一下help,应该是通过发送消息
wm_cap_set_callback_videostream,来设置的,或者调用宏capsetcallbackonvideostream
来完成的。这样设定之后,系统在进行图像捕捉的过程中,就会自动调用你写的回调函数。
这个回调函数的函数体需要你自已来写,然后在另一函数中调用,即是说:
lresult callback capvideostreamcallback(hwnd hwnd,lpvideohdr lpvhdr)
{
........
}
//在另一函数中调用它时,通过从a里面传递过来的foo的地址调用foo,通知a发生了什么事情,让a作出相应反应。
那么我们就把foo称为回调函数。
“这个回调函数不是vfw.h中声明的么,“
—-那是声明了回调函数原型,是告诉你传递进来的回调函数必须和它定义的原型保持一致。
”为什么要自己写函数体呢?“
—-比如在上面模块b里面,它只知道当event发生时,向模块a发出通知,具体怎么回应这个事件,不是b所关心的,也不是b所能预料到的。
你站在a的角度上思考,当然要你自己作出对event的反应,也就是你要自己写函数体。
你如果明白了c++里面的函数指针,就很容易理解回调函数了。
“不知道系统调用后有什么结果,或者我怎么利用这个结果啊”
—如果你向系统传递一个回调函数地址,那么你的程序就相当于上面我说的模块a,系统就相当于模块b,系统只是调用你的函数,它根本不可能知道会有什么结果。
你怎么利用这个结果,看你是怎么定义这个回调函数的。
回调函数和回调机制是不同的概念,。,,函数是被调用的,但是回调机制在不同的语言中不都是以函数指针来实现的。。。。比如c#…一般的在windows api 中,会调都是使用函数指针实现的。。。
发表者:seu_why
函数调用方式
我们知道在进行函数调用时,有几种调用方法,主要分为c式,pascal式.在c和c++中
c式调用是缺省的,类的成员函数缺省调用为_stdcall。二者是有区别的,下面我们用
实例说明一下:
1. __cdecl :c和c++缺省调用方式
例子:
void input( int &m,int &n);/*相当于void __cdecl input(int &m,int &n);*/
以下是相应的汇编代码:
00401068 lea eax,[ebp-8] ;取[ebp-8]地址(ebp-8),存到eax
0040106b push eax ;然后压栈
0040106c lea ecx,[ebp-4] ;取[ebp-4]地址(ebp-4),存到ecx
0040106f push ecx ;然后压栈
00401070 call @ilt+5(input) (0040100a);然后调用input函数
00401075 add esp,8 ;恢复栈
从以上调用input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然
后压栈ebp-4,然后调用函数input,最后input函数调用结束后,利用esp+8恢复栈。由
此可见,在c语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复
堆栈。
下面看一下:地址ebp-8和ebp-4是什么?
在vc的view下选debug windows,然后选registers,显示寄存器变量值,然后在
选debug windows下面的memory,输入ebp-8的值和ebp-4的值(或直接输入ebp-8和-4),
看一下这两个地址实际存储的是什么值,实际上是变量 n 的地址(ebp-8),m的地址
(ebp-4),由此可以看出:在主调用函数中进行实参的压栈并且顺序是从右到左。另外,
由于实参是相应的变量的引用,也证明实际上引用传递的是变量的地址(类似指针)。
总结:在c或c++语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且
恢复堆栈,实参的压栈顺序是从右到左,最后由主调函数进行堆栈恢复。由于主调用
函数管理堆栈,所以可以实现变参函数。另外,命名修饰方法是在函数前加一个下划
线(_).
2. winapi (实际上就是pascal,callback,_stdcall)
例子:
void winapi input( int &m,int &n);
看一下相应调用的汇编代码:
00401068 lea eax,[ebp-8]
0040106b push eax
0040106c lea ecx,[ebp-4]
0040106f push ecx
00401070 call @ilt+5(input) (0040100a)
从以上调用input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然
后压栈ebp-4,然后调用函数input,在调用函数input之后,没有相应的堆栈恢复工作
(为其它的函数调用,所以我没有列出)
下面再列出input函数本身的汇编代码:(实际此函数不大,但做汇编例子还是大了些,
大家可以只看前和后,中间代码与此例子无关)
39:
void winapi input( int &m,int &n)
40:
{
00401110 push ebp
00401111 mov ebp,esp
00401113 sub esp,48h
00401116 push ebx
00401117 push esi
00401118 push edi
00401119 lea edi,[ebp-48h]
0040111c mov ecx,12h
00401121 mov eax,0cccccccch
00401126 rep stos dword ptr [edi]
41: int s,i;
42:
43: while(1)
00401128 mov eax,1
0040112d test eax,eax
0040112f je input+0c1h (004011d1)
44: {
45: printf("/nplease input the first number m:");
00401135 push offset string "/nplease input the first number m"... (004260b8)
0040113a call printf (00401530)
0040113f add esp,4
46: scanf("%d",&m);
00401142 mov ecx,dword ptr [ebp+8]
00401145 push ecx
00401146 push offset string "%d" (004260b4)
0040114b call scanf (004015f0)
00401150 add esp,8
47:
48: if ( m= s )
004011b3 mov eax,dword ptr [ebp+8]
004011b6 mov ecx,dword ptr [eax]
004011b8 cmp ecx,dword ptr [ebp-4]
004011bb jl input+0afh (004011bf)
57: break;
004011bd jmp input+0c1h (004011d1)
58: else
59: printf(" m
最后,我们看到在函数末尾部分,有ret 8,明显是恢复堆栈,由于在32位c++中,
变量地址为4个字节(int也为4个字节),所以弹栈两个地址即8个字节。
由此可以看出:在主调用函数中负责压栈,在被调用函数中负责恢复堆栈。因此不能
实现变参函数,因为被调函数不能事先知道弹栈数量,但在主调函数中是可以做到的,
因为参数数量由主调函数确定。
下面再看一下,ebp-8和ebp-4这两个地址实际存储的是什么值,ebp-8地址存储的是n
的值,ebp -4存储的是m的值。说明也是从右到左压栈,进行参数传递。
总结:在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且
负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法
是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数
所占字节数(10进制),如:void input(int &m,int &n),被修饰成:_input@8
对于大多数api函数以及窗口消息处理函数皆用 callback ,所以调用前,主调函数会
先压栈,然后api函数自己恢复堆栈。
如:
push edx
push edi
push eax
push ebx
call getdlgitemtexta
//-------------------------------------------------------------------------
rundll32.exe 的回调函数在msdn上的申明为
void callback entrypoint(
hwnd hwnd, // handle to owner window
hinstance hinst, // instance handle for the dll
lptstr lpcmdline, // string the dll will parse
int ncmdshow // show state
);
但是,当我们的dll工程设置了?]认调用规则为stdcall时就会出错,
因此,实际定义这种导出函数时,应该这样申明
extern "c" __declspec(dllexport)
void __cdecl entrypoint(
hwnd hwnd, // handle to owner window
hinstance hinst, // instance handle for the dll
lptstr lpcmdline, // string the dll will parse
int ncmdshow // show state
)
关于CALLBACK函数2006-12-28 13:53
callback函数一般不需要你去直接调用。它一般用作参数去调用某些
API函数。如使用SetTimer函数时,如果你不想让窗口来响应WM_TIMER消息
可以用一个callback函数为参数去调用该函数,当定时器事件发生时,Windows
将自动调用你提供的这个callback函数来响应WM_TIMER消息。
再有,声音采集的时候也可以使用callback函数。为打开波形声音输入设备
而调用waveInOpen()时,可以把一个callback函数作为参数。调用waveInStart( )
启动录音过程后,当指定的波形声音准备好后,Windows系统将被通知去调用这个
callback函数,进行数据的后处理,如写入文件等。显然,你在启动录音过程后
无须使程序挂起去等待声音数据准备好,而可以接着执行以下的代码。
实际上callback函数给你提供了一种异步的程序执行逻辑。你事先准备好
要执行的代码,并通过API调用告诉Windows,而当相应事件发生时,Windows将被告知
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 一个线上全文索引BUG的排查:关于类阿拉件数字的分词与检索
说到全文检索的分词,多半讲到的是中(日韩)文分词,少有英文等拉丁文系语言,因为英语单词天然就是分词的。 但更少讲到阿拉伯数字。比如金额,手机号码,座机号码等等。 以下不是传统的从0开始针对mysql全文索引前世今生讲起。 我更喜欢从一个小问题入手,见缝插针的将…