[轉]C++回調函數(callback)的使用

原文地址:http://blog.sina.com.cn/s/blog_6568e7880100p77y.htmlhtml

什麼是回調函數(callback)
    模塊A有一個函數foo,他向模塊B傳遞foo的地址,而後在B裏面發生某種事件(event)時,經過從A裏面傳遞過來的foo的地址調用foo,通知A發生了什麼事情,讓A做出相應反應。 那麼咱們就把foo稱爲回調函數。
   
例子:
      回調函數是個頗有用,也很重要的概念。當發生某種事件時,系統或其餘函數將會自動調用您定義的一段函數。回調函數在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;            
       以上函數爲全局函數,假如要使用一個類裏的函數做爲回調函數原形,把該類函數聲明爲靜態函數便可。 [Page]

三: 回調函數調用調用者
          調用回調函數的函數我把他放到了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; [Page]
               DWORD StartTick;
           //一個延時循環
              for(;Step<200;Step++)
              {
                 StartTick = GetTickCount();
                  
                     for(;GetTickCount()-StartTick<10;)
                     {
                       if(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE) )
                           {
                             TranslateMessage(&Msg);
                                 DispatchMessage(&Msg);
                           }
                     }                                
                      
               sprintf(Buffer,/"Running d/",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;             [Page]
       }   
          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)裏專心實現模塊核心的業務流程和技術功能,外圍的擴展的功能只給出一個回調函數的接口,經過調用其餘模塊傳遞過來的回調函數 地址的方式,將後續處理無縫地交給另外一個模塊,隨他按自定義的方式處理。
       本文的例子使用了在DLL裏的多線程延時後調用回調函數的方式,只是爲了突出一下回調函數的效果,其實只要是在本進程以內,都可以隨您高興可以把函數地址傳遞來傳遞去,當成回調函數使用。
        這樣的編程模式原理很簡單單一:就是把函數也當作一個指針一個地址來調用,沒有什麼別的複雜的東西,僅僅是編程裏的一個小技巧。至於回調函數模式究竟能爲您帶來多少好處,就看您是否使用,怎樣使用這種編程模式了。
另外的解釋:cdxiaogan
msdn上這麼說的:
有關函數指針的知識
使用例子可以很好地說明函數指針的用法。首先,看一看 Win32 API 中的 EnumWindows 函數:
Declare Function EnumWindows lib /"user32/" _
(ByVal lpEnumFunc as Long, _
ByVal lParam as Long ) As Long
EnumWindows 是個枚舉函數,他可以列出系統中每個打開的窗口的句柄。EnumWindows 的工做方式是重複地調用傳遞給他的第一個參數(lpEnumFunc,函數指針)。每當 EnumWindows 調用函數,EnumWindows 都傳遞一個打開窗口的句柄。
在代碼中調用 EnumWindows 時,可以將一個自定義函數做爲第一個參數傳遞給他,用來處理一系列的值。例如,可以編寫一個函數將任何的值添加到一個列表框中,將 hWnd 值轉換爲窗口的名字,連同其餘任何操做!
爲了代表傳遞的參數是個自定義函數,在函數名稱的前面要加上 AddressOf 關鍵字。第二個參數可以是合適的任何值。例如,假如要把 MyProc 做爲函數參數,可以按下面的方式調用 EnumWindows:
x = EnumWindows(AddressOf MyProc, 5)
在調用過程時指定的自定義函數被稱爲回調函數。回調函數(一般簡稱爲「回調」)可以對過程提供的數據執行指定的操做。
回調函數的參數集必須具有規定的形式,這是由使用回調函數的 API 決定的。關於須要什麼參數,怎樣調用他們,請參閱 API 文檔。
回覆人:zcchm
我談一下本身對回調函數的一點理解, 不對的地方請指教.
     我剛開始接觸回調時, 也是一團霧水.不少人解釋這個問題時, 老是拿API來舉例子, 原本菜鳥最害怕的就是API, ^_^. 回調跟API沒有必然聯繫.
     其實回調就是一種利用函數指針進行函數調用的過程.
    
     爲何要用回調呢?好比我要寫一個子模塊給您用, 來接收遠程socket發來的命令.當我接收到命令後, 須要調用您的主模塊的函數, 來進行相應的處理.可是我不知道您要用哪一個函數來處理這個命令,   我也不知道您的主模塊是什麼.cpp或.h, 或說, 我根本不用關心您在主模塊裏怎麼處理他, 也不該該關心用什麼函數處理他...... 怎麼辦?



     使用回調.
     我在個人模塊裏先定義回調函數類型, 連同回調函數指針.
     typedef void (CALLBACK *cbkSendCmdToMain) (AnsiString sCmd);
     cbkSendCmdToMain     SendCmdToMain;
     這樣SendCmdToMain就是個指向擁有一個AnsiString形參, 返回值爲void的函數指針.
     這樣, 在我接收到命令時, 就可以調用這個函數啦.
     ...
     SendCmdToMain(sCommand);
     ...
     可是這樣還不夠, 我得給一個接口函數(好比Init), 讓您在主模塊裏調用Init來註冊這個回調函數.
     在您的主模塊裏, 可能這樣
     void CALLBACK YourSendCmdFun(AnsiString sCmd);   //聲明
     ...
     void CALLBACK YourSendCmdFun(AnsiString sCmd);   //定義
     {
         ShowMessage(sCmd);
     }
     ...
     調用Init函數向個人模塊註冊回調.可能這樣:
     Init(YourSendCmdFun, ...);
     這樣, 預期目的就達到了.

     須要注意一點, 回調函數通常都要聲明爲全局的. 假如要在類裏使用回調函數, 前面須要加上 static   , 其實也至關於全局的.編程

相關文章
相關標籤/搜索