c回呼函數(真不好理解)

來源:互聯網
上載者:User

標籤:

什麼是回呼函數(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);

[NextPage]

         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); 
                                    } 
                            }                                 [Page]
                       /*把運行情況列印到案頭,這是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);

[NextPage]


             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裡的多線程延時後調用回呼函數的方式,只是為了突出一下回呼函數的效果,其實只要是在本進程之內,都可以隨你高興可以把函數地址傳遞來傳遞去,當成回呼函數使用。 
        這樣的編程模式原理非常簡單單一:就是把函數也看成一個指標一個地址來調用,沒有什麼別的複雜的東西,僅僅是編程裡的一個小技巧。至於回呼函數模式究竟能為你帶來多少好處,就看你是否使用,如何使用這種編程模式了。 [Page]
另外的解釋: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, 或者說, 我根本不用關心你在主模組裡怎麼處理它, 也不應該關心用什麼函數處理它...... 怎麼辦? 

[NextPage]


     使用回調. 
     我在我的模組裡先定義回呼函數類型, 以及回呼函數指標. 
     typedef void (CALLBACK *cbkSendCmdToMain) (AnsiString sCmd); 
     cbkSendCmdToMain     SendCmdToMain; 
     這樣SendCmdToMain就是一個指向擁有一個AnsiString形參, 返回值為void的函數指標. 
     這樣, 在我接收到命令時, 就可以調用這個函數啦. [Page]
     ... 
     SendCmdToMain(sCommand); 
     ... 
     但是這樣還不夠, 我得給一個介面函數(比如Init), 讓你在主模組裡調用Init來註冊這個回呼函數. 
     在你的主模組裡, 可能這樣 
     void CALLBACK YourSendCmdFun(AnsiString sCmd);   //聲明 
     ... 
     void CALLBACK YourSendCmdFun(AnsiString sCmd);   //定義 
     { 
         ShowMessage(sCmd); 
     } 
     ... 
     調用Init函數向我的模組註冊回調.可能這樣: 
     Init(YourSendCmdFun, ...); 
     這樣, 預期目的就達到了. 

     需要注意一點, 回呼函數一般都要聲明為全域的. 如果要在類裡使用回呼函數, 前面需要加上 static   , 其實也相當於全域的.

c回呼函數(真不好理解)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.