CallBack函数及其控制

本文详细介绍了回调函数的概念及其在Windows编程中的应用。通过VC和Delphi混合编程实例,展示了如何使用回调函数实现模块间的异步通信及事件处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在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<200;step++)  
      {  
          starttick   =   gettickcount();  
          /*这一段为线程交出部分运行时间以让系统处理其他事务*/  
          for(;gettickcount()-starttick<10;)  
          {  
              if(peekmessage(&msg,null,0,0,pm_noremove)   )  
              {  
                  translatemessage(&msg);  
                  dispatchmessage(&msg);  
              }  
          }  
          /*把运行情况打印到桌面,这是vcbear调试程序时最喜欢干的事情*/  
          sprintf(buffer,"running   %04d",step);  
          if(hdc!=null)  
              textout(hdc,30,50,buffer,strlen(buffer));  
      }  
   
      /*延时一段时间后调用回调函数*/  
      (*gcallback)(step,1);  
   
      /*结束*/  
      ::releasedc   (hwnd_desktop,hdc);  
      return   0;  
  }  
   
  五:万事具备  
      使用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   <   n*(n+1)/2,please   input   again!/n");  
  004011bf   push   offset   string   "   m   <   n*(n+1)/2,please   input   agai"...   (00426060)  
  004011c4   call   printf   (00401530)  
  004011c9   add   esp,4  
  60:   }  
  004011cc   jmp   input+18h   (00401128)  
  61:  
  62:   }  
  004011d1   pop   edi  
  004011d2   pop   esi  
  004011d3   pop   ebx  
  004011d4   add   esp,48h  
  004011d7   cmp   ebp,esp  
  004011d9   call   __chkesp   (004015b0)  
  004011de   mov   esp,ebp  
  004011e0   pop   ebp  
  004011e1   ret   8  
     
          最后,我们看到在函数末尾部分,有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将被告知 

vfp9.0调用API制作照相软件实例 PUBLIC WM_CAP_DRIVER_DISCONNECT PUBLIC hwndc,WM_CAP_SAVEDIB,WM_CAP_FILE_SET_CAPTURE_FILEA,WM_CAP_SEQUENCE,WM_CAP_STOP DECLARE INTEGER capCreateCaptureWindowA IN "AVICAP32.DLL" STRING lpszWindowName ,INTEGER dwStyle , INTEGER x, INTEGER Y, INTEGER nWidth ,INTEGER nHeight,INTEGER ParentWin,INTEGER nId DECLARE INTEGER SendMessage IN "user32" INTEGER HWND, INTEGER wmsg,INTEGER wpar1, INTEGER wpar2 DECLARE INTEGER SendMessage IN "user32" AS SendMessageA INTEGER HWND, INTEGER wmsg,INTEGER wpar1, STRING wpar2 *!* 显示: WM_USER = 1024 WM_CAP_START = WM_USER WM_CAP_STOP = WM_CAP_START + 68 WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10 WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11 WM_CAP_SAVEDIB = WM_CAP_START + 25 WM_CAP_GRAB_FRAME = WM_CAP_START + 60 WM_CAP_SEQUENCE = WM_CAP_START + 62 WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20 WM_CAP_SEQUENCE_NOFILE =WM_CAP_START+ 63 WM_CAP_SET_OVERLAY =WM_CAP_START+ 51 WM_CAP_SET_PREVIEW =WM_CAP_START+ 50 WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_CAP_START +6 WM_CAP_SET_CALLBACK_ERROR=WM_CAP_START +2 WM_CAP_SET_CALLBACK_STATUSA= WM_CAP_START +3 WM_CAP_SET_CALLBACK_FRAME= WM_CAP_START +5 WM_CAP_SET_SCALE=WM_CAP_START+ 53 WM_CAP_SET_PREVIEWRATE=WM_CAP_START+ 52 hWndC = capCreateCaptureWindowA('My Own Capture Window',1342177280,0,0,200,150,THISFORM.HWND ,0) &&显示大小 IF hWndC 0 SendMessage(hWndC, WM_CAP_SET_CALLBACK_VIDEOSTREAM, 0, 0) SendMessage(hWndC, WM_CAP_SET_CALLBACK_ERROR, 0, 0) SendMessage(hWndC, WM_CAP_SET_CALLBACK_STATUSA, 0, 0) SendMessage(hWndC, WM_CAP_DRIVER_CONNECT, 0, 0) SendMessage(hWndC, WM_CAP_SET_SCALE, 1, 0) SendMessage(hWndC, WM_CAP_SET_PREVIEWRATE, 66, 0) SendMessage(hWndC, WM_CAP_SET_OVERLAY, 1, 0) SendMessage(hWndC, WM_CAP_SET_PREVIEW, 1, 0) * THISFORM.ACTIVATE ENDIF
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值