什麼是回調函數

 

簡單地說:被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調
找了點回調函數的東西

序言

看完下面的專家解答你就明白了

Q :
編程工具: C++ BUILDER 3.0
操作系統: WIN98
我想在C++ 中使用回調函數,請問它的內在機制如何,另外怎麼定義。我用DialogBox函數時,如何使用回調函數? 它和鉤子函數有何不同?多謝指教!!!拜託!!!

A:

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK,這主要是說明該函數的調用方式。DialogBox的回調函數實際上是個窗口過程,用來處理所有消息。其定義爲:
BOOL CALLBACK DialogProc(

HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有詳細說明。一般使用C++ Builder或MFC的往往沒有使用SDK編程的經驗,建議找一些SDK編程的書看一下,否則很難理解如何使用窗口過程。
至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

frank的意見:
我對回調函數的理解雖然粗淺,但是我覺得會讓人更容易理解:回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。
聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統。不要把它當作你的某個類的成員函數。

ping的意見:
frank說:回調函數屬於WINDOWS系統。我覺得不應該說回調函數是屬於系統的。應該說是程序把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因爲消息是系統一手掌握着的,由系統來調用我們的程序對消息的處理部分,這樣子會比較方便。不然我們又得花力氣去讀消息列表了。

簡單的話:你來定義,系統調用.系統自有辦法.

正文


一,回調函數

我們經常在C++設計時通過使用回調函數可以使有些應用(如定時器事件回調處理、用回調函數記錄某操作進度等)變得非常方便和符合邏輯,那麼它的內在機制如何呢,怎麼定義呢?它和其它函數(比如鉤子函數)有何不同呢?

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。

而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK(相當於FAR PASCAL),這主要是說明該函數的調用方式。

至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

也可以這樣,更容易理解:回調函數就好像是一箇中斷處理函數,系統在符合你設定的條件時自動調用。爲此,你需要做三件事:

1.       聲明;

2.       定義;

3.       設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。

聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統,不要把它當作你的某個類的成員函數。


二,回調函數、消息和事件例程


    調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件。

    此調用機制並非完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並非如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

    回調函數使程序結構亂了許多。Windows API 函數集中有不少回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。

無論何種事物,能以樹形結構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜往往需要構成網狀結構,非簡單的客戶/服務器關係能窮盡。

    Windows 系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的 WParam 和 LParam 相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows 發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程 B 收到進程 A 發來的消息,啓動了一段代碼,其中又向進程 A 發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

    利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口 handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借 API 函數 SendMessage 向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借 API 函數 PostMessage 發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

    如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。

    不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但並不會使程序減速。
(例程似乎是進程的臺灣翻譯。)


三,精妙比喻:回調函數還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

       回調用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調,例如作爲一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。其實回調和API非常接近,他們的共性都是跨層調用的函數。但區別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來保存這個回調,在需要調用時,只需引用這個函數指針和相關的參數指針。    其實:回調就是該函數寫在高層,低層通過一個函數指針保存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數。



    軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎。
   
    對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。

    對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。

    在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。

    Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。

    對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。

    下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。

    2 過程語言中的回調(C)


    2.1 函數指針
    回調在C語言中是通過函數指針來實現的,通過將回調函數的地址傳給被調函數從而實現回調。因此,要實現回調,必須首先定義函數指針,請看下面的例子:

    void Func(char *s);// 函數原型
    void (*pFunc) (char *);//函數指針

    可以看出,函數的定義和函數指針的定義非常類似。

    一般的化,爲了簡化函數指針類型的變量定義,提高程序的可讀性,我們需要把函數指針類型自定義一下。

    typedef void(*pcb)(char *);

    回調函數可以象普通函數一樣被程序調用,但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。

    被調函數的例子:

    void GetCallBack(pcb callback)
    {
    /*do something*/
    }
    用戶在調用上面的函數時,需要自己實現一個pcb類型的回調函數:
    void fCallback(char *s)
    {
    /* do something */
    }
    然後,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
    GetCallBack(fCallback);

    如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。

    2.2 參數傳遞規則
    到目前爲止,我們只討論了函數指針及回調而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種調用規範。如在Visual C++中,可以在函數類型前加_cdecl,_stdcall或者_pascal來表示其調用規範(默認爲_cdecl)。C++ Builder也支持_fastcall調用規範。調用規範影響編譯器產生的給定函數名,參數傳遞的順序(從右到左或從左到右),堆棧清理責任(調用者或者被調用者)以及參數傳遞機制(堆棧,CPU寄存器等)。

    將調用規範看成是函數類型的一部分是很重要的;不能用不兼容的調用規範將地址賦值給函數指針。例如:

    // 被調用函數是以int爲參數,以int爲返回值
    __stdcall int callee(int);

    // 調用函數以函數指針爲參數
    void caller( __cdecl int(*ptr)(int));

    // 在p中企圖存儲被調用函數地址的非法操作
    __cdecl int(*p)(int) = callee; // 出錯

    指針p和callee()的類型不兼容,因爲它們有不同的調用規範。因此不能將被調用者的地址賦值給指針p,儘管兩者有相同的返回值和參數列

    2.3 應用舉例
    C語言的標準庫函數中很多地方就採用了回調函數來讓用戶定製處理過程。如常用的快速排序函數、二分搜索函數等。

    快速排序函數原型:

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
    二分搜索函數原型:
    void *bsearch(const void *key, const void *base, size_t nelem,
    size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    其中fcmp就是一個回調函數的變量。

    下面給出一個具體的例子:

    #include <stdio.h>
    #include <stdlib.h>

    int sort_function( const void *a, const void *b);
    int list[5] = { 54, 21, 11, 67, 22 };

    int main(void)
    {
 int x;

 qsort((void *)list, 5, sizeof(list[0]), sort_function);
 for (x = 0; x < 5; x++)
 printf("%i/n", list[x]);
 return 0;
 }

 int sort_function( const void *a, const void *b)
 {
 return *(int*)a-*(int*)b;
 }

 2.4 面嚮對象語言中的回調(Delphi)

 Dephi與C++一樣,爲了保持與過程語言Pascal的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。

 

簡單地說:被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調
找了點回調函數的東西

序言

看完下面的專家解答你就明白了

Q :
編程工具: C++ BUILDER 3.0
操作系統: WIN98
我想在C++ 中使用回調函數,請問它的內在機制如何,另外怎麼定義。我用DialogBox函數時,如何使用回調函數? 它和鉤子函數有何不同?多謝指教!!!拜託!!!

A:

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK,這主要是說明該函數的調用方式。DialogBox的回調函數實際上是個窗口過程,用來處理所有消息。其定義爲:
BOOL CALLBACK DialogProc(

HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有詳細說明。一般使用C++ Builder或MFC的往往沒有使用SDK編程的經驗,建議找一些SDK編程的書看一下,否則很難理解如何使用窗口過程。
至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

frank的意見:
我對回調函數的理解雖然粗淺,但是我覺得會讓人更容易理解:回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。
聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統。不要把它當作你的某個類的成員函數。

ping的意見:
frank說:回調函數屬於WINDOWS系統。我覺得不應該說回調函數是屬於系統的。應該說是程序把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因爲消息是系統一手掌握着的,由系統來調用我們的程序對消息的處理部分,這樣子會比較方便。不然我們又得花力氣去讀消息列表了。

簡單的話:你來定義,系統調用.系統自有辦法.

正文


一,回調函數

我們經常在C++設計時通過使用回調函數可以使有些應用(如定時器事件回調處理、用回調函數記錄某操作進度等)變得非常方便和符合邏輯,那麼它的內在機制如何呢,怎麼定義呢?它和其它函數(比如鉤子函數)有何不同呢?

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。

而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK(相當於FAR PASCAL),這主要是說明該函數的調用方式。

至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

也可以這樣,更容易理解:回調函數就好像是一箇中斷處理函數,系統在符合你設定的條件時自動調用。爲此,你需要做三件事:

1.       聲明;

2.       定義;

3.       設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。

聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統,不要把它當作你的某個類的成員函數。


二,回調函數、消息和事件例程


    調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件。

    此調用機制並非完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並非如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

    回調函數使程序結構亂了許多。Windows API 函數集中有不少回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。

無論何種事物,能以樹形結構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜往往需要構成網狀結構,非簡單的客戶/服務器關係能窮盡。

    Windows 系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的 WParam 和 LParam 相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows 發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程 B 收到進程 A 發來的消息,啓動了一段代碼,其中又向進程 A 發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

    利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口 handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借 API 函數 SendMessage 向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借 API 函數 PostMessage 發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

    如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。

    不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但並不會使程序減速。
(例程似乎是進程的臺灣翻譯。)


三,精妙比喻:回調函數還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

       回調用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調,例如作爲一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。其實回調和API非常接近,他們的共性都是跨層調用的函數。但區別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來保存這個回調,在需要調用時,只需引用這個函數指針和相關的參數指針。    其實:回調就是該函數寫在高層,低層通過一個函數指針保存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數。



    軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎。
   
    對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。

    對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。

    在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。

    Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。

    對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。

    下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。

    2 過程語言中的回調(C)


    2.1 函數指針
    回調在C語言中是通過函數指針來實現的,通過將回調函數的地址傳給被調函數從而實現回調。因此,要實現回調,必須首先定義函數指針,請看下面的例子:

    void Func(char *s);// 函數原型
    void (*pFunc) (char *);//函數指針

    可以看出,函數的定義和函數指針的定義非常類似。

    一般的化,爲了簡化函數指針類型的變量定義,提高程序的可讀性,我們需要把函數指針類型自定義一下。

    typedef void(*pcb)(char *);

    回調函數可以象普通函數一樣被程序調用,但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。

    被調函數的例子:

    void GetCallBack(pcb callback)
    {
    /*do something*/
    }
    用戶在調用上面的函數時,需要自己實現一個pcb類型的回調函數:
    void fCallback(char *s)
    {
    /* do something */
    }
    然後,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
    GetCallBack(fCallback);

    如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。

    2.2 參數傳遞規則
    到目前爲止,我們只討論了函數指針及回調而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種調用規範。如在Visual C++中,可以在函數類型前加_cdecl,_stdcall或者_pascal來表示其調用規範(默認爲_cdecl)。C++ Builder也支持_fastcall調用規範。調用規範影響編譯器產生的給定函數名,參數傳遞的順序(從右到左或從左到右),堆棧清理責任(調用者或者被調用者)以及參數傳遞機制(堆棧,CPU寄存器等)。

    將調用規範看成是函數類型的一部分是很重要的;不能用不兼容的調用規範將地址賦值給函數指針。例如:

    // 被調用函數是以int爲參數,以int爲返回值
    __stdcall int callee(int);

    // 調用函數以函數指針爲參數
    void caller( __cdecl int(*ptr)(int));

    // 在p中企圖存儲被調用函數地址的非法操作
    __cdecl int(*p)(int) = callee; // 出錯

    指針p和callee()的類型不兼容,因爲它們有不同的調用規範。因此不能將被調用者的地址賦值給指針p,儘管兩者有相同的返回值和參數列

    2.3 應用舉例
    C語言的標準庫函數中很多地方就採用了回調函數來讓用戶定製處理過程。如常用的快速排序函數、二分搜索函數等。

    快速排序函數原型:

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
    二分搜索函數原型:
    void *bsearch(const void *key, const void *base, size_t nelem,
    size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    其中fcmp就是一個回調函數的變量。

    下面給出一個具體的例子:

    #include <stdio.h>
    #include <stdlib.h>

    int sort_function( const void *a, const void *b);
    int list[5] = { 54, 21, 11, 67, 22 };

    int main(void)
    {
 int x;

 qsort((void *)list, 5, sizeof(list[0]), sort_function);
 for (x = 0; x < 5; x++)
 printf("%i/n", list[x]);
 return 0;
 }

 int sort_function( const void *a, const void *b)
 {
 return *(int*)a-*(int*)b;
 }

 2.4 面嚮對象語言中的回調(Delphi)

 Dephi與C++一樣,爲了保持與過程語言Pascal的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。

 

簡單地說:被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調
找了點回調函數的東西

序言

看完下面的專家解答你就明白了

Q :
編程工具: C++ BUILDER 3.0
操作系統: WIN98
我想在C++ 中使用回調函數,請問它的內在機制如何,另外怎麼定義。我用DialogBox函數時,如何使用回調函數? 它和鉤子函數有何不同?多謝指教!!!拜託!!!

A:

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK,這主要是說明該函數的調用方式。DialogBox的回調函數實際上是個窗口過程,用來處理所有消息。其定義爲:
BOOL CALLBACK DialogProc(

HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有詳細說明。一般使用C++ Builder或MFC的往往沒有使用SDK編程的經驗,建議找一些SDK編程的書看一下,否則很難理解如何使用窗口過程。
至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

frank的意見:
我對回調函數的理解雖然粗淺,但是我覺得會讓人更容易理解:回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。
聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統。不要把它當作你的某個類的成員函數。

ping的意見:
frank說:回調函數屬於WINDOWS系統。我覺得不應該說回調函數是屬於系統的。應該說是程序把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因爲消息是系統一手掌握着的,由系統來調用我們的程序對消息的處理部分,這樣子會比較方便。不然我們又得花力氣去讀消息列表了。

簡單的話:你來定義,系統調用.系統自有辦法.

正文


一,回調函數

我們經常在C++設計時通過使用回調函數可以使有些應用(如定時器事件回調處理、用回調函數記錄某操作進度等)變得非常方便和符合邏輯,那麼它的內在機制如何呢,怎麼定義呢?它和其它函數(比如鉤子函數)有何不同呢?

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。

而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK(相當於FAR PASCAL),這主要是說明該函數的調用方式。

至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

也可以這樣,更容易理解:回調函數就好像是一箇中斷處理函數,系統在符合你設定的條件時自動調用。爲此,你需要做三件事:

1.       聲明;

2.       定義;

3.       設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。

聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統,不要把它當作你的某個類的成員函數。


二,回調函數、消息和事件例程


    調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件。

    此調用機制並非完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並非如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

    回調函數使程序結構亂了許多。Windows API 函數集中有不少回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。

無論何種事物,能以樹形結構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜往往需要構成網狀結構,非簡單的客戶/服務器關係能窮盡。

    Windows 系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的 WParam 和 LParam 相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows 發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程 B 收到進程 A 發來的消息,啓動了一段代碼,其中又向進程 A 發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

    利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口 handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借 API 函數 SendMessage 向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借 API 函數 PostMessage 發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

    如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。

    不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但並不會使程序減速。
(例程似乎是進程的臺灣翻譯。)


三,精妙比喻:回調函數還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

       回調用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調,例如作爲一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。其實回調和API非常接近,他們的共性都是跨層調用的函數。但區別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來保存這個回調,在需要調用時,只需引用這個函數指針和相關的參數指針。    其實:回調就是該函數寫在高層,低層通過一個函數指針保存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數。



    軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎。
   
    對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。

    對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。

    在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。

    Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。

    對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。

    下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。

    2 過程語言中的回調(C)


    2.1 函數指針
    回調在C語言中是通過函數指針來實現的,通過將回調函數的地址傳給被調函數從而實現回調。因此,要實現回調,必須首先定義函數指針,請看下面的例子:

    void Func(char *s);// 函數原型
    void (*pFunc) (char *);//函數指針

    可以看出,函數的定義和函數指針的定義非常類似。

    一般的化,爲了簡化函數指針類型的變量定義,提高程序的可讀性,我們需要把函數指針類型自定義一下。

    typedef void(*pcb)(char *);

    回調函數可以象普通函數一樣被程序調用,但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。

    被調函數的例子:

    void GetCallBack(pcb callback)
    {
    /*do something*/
    }
    用戶在調用上面的函數時,需要自己實現一個pcb類型的回調函數:
    void fCallback(char *s)
    {
    /* do something */
    }
    然後,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
    GetCallBack(fCallback);

    如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。

    2.2 參數傳遞規則
    到目前爲止,我們只討論了函數指針及回調而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種調用規範。如在Visual C++中,可以在函數類型前加_cdecl,_stdcall或者_pascal來表示其調用規範(默認爲_cdecl)。C++ Builder也支持_fastcall調用規範。調用規範影響編譯器產生的給定函數名,參數傳遞的順序(從右到左或從左到右),堆棧清理責任(調用者或者被調用者)以及參數傳遞機制(堆棧,CPU寄存器等)。

    將調用規範看成是函數類型的一部分是很重要的;不能用不兼容的調用規範將地址賦值給函數指針。例如:

    // 被調用函數是以int爲參數,以int爲返回值
    __stdcall int callee(int);

    // 調用函數以函數指針爲參數
    void caller( __cdecl int(*ptr)(int));

    // 在p中企圖存儲被調用函數地址的非法操作
    __cdecl int(*p)(int) = callee; // 出錯

    指針p和callee()的類型不兼容,因爲它們有不同的調用規範。因此不能將被調用者的地址賦值給指針p,儘管兩者有相同的返回值和參數列

    2.3 應用舉例
    C語言的標準庫函數中很多地方就採用了回調函數來讓用戶定製處理過程。如常用的快速排序函數、二分搜索函數等。

    快速排序函數原型:

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
    二分搜索函數原型:
    void *bsearch(const void *key, const void *base, size_t nelem,
    size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    其中fcmp就是一個回調函數的變量。

    下面給出一個具體的例子:

    #include <stdio.h>
    #include <stdlib.h>

    int sort_function( const void *a, const void *b);
    int list[5] = { 54, 21, 11, 67, 22 };

    int main(void)
    {
 int x;

 qsort((void *)list, 5, sizeof(list[0]), sort_function);
 for (x = 0; x < 5; x++)
 printf("%i/n", list[x]);
 return 0;
 }

 int sort_function( const void *a, const void *b)
 {
 return *(int*)a-*(int*)b;
 }

 2.4 面嚮對象語言中的回調(Delphi)

 Dephi與C++一樣,爲了保持與過程語言Pascal的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。

 

簡單地說:被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調
找了點回調函數的東西

序言

看完下面的專家解答你就明白了

Q :
編程工具: C++ BUILDER 3.0
操作系統: WIN98
我想在C++ 中使用回調函數,請問它的內在機制如何,另外怎麼定義。我用DialogBox函數時,如何使用回調函數? 它和鉤子函數有何不同?多謝指教!!!拜託!!!

A:

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK,這主要是說明該函數的調用方式。DialogBox的回調函數實際上是個窗口過程,用來處理所有消息。其定義爲:
BOOL CALLBACK DialogProc(

HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有詳細說明。一般使用C++ Builder或MFC的往往沒有使用SDK編程的經驗,建議找一些SDK編程的書看一下,否則很難理解如何使用窗口過程。
至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

frank的意見:
我對回調函數的理解雖然粗淺,但是我覺得會讓人更容易理解:回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。
聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統。不要把它當作你的某個類的成員函數。

ping的意見:
frank說:回調函數屬於WINDOWS系統。我覺得不應該說回調函數是屬於系統的。應該說是程序把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因爲消息是系統一手掌握着的,由系統來調用我們的程序對消息的處理部分,這樣子會比較方便。不然我們又得花力氣去讀消息列表了。

簡單的話:你來定義,系統調用.系統自有辦法.

正文


一,回調函數

我們經常在C++設計時通過使用回調函數可以使有些應用(如定時器事件回調處理、用回調函數記錄某操作進度等)變得非常方便和符合邏輯,那麼它的內在機制如何呢,怎麼定義呢?它和其它函數(比如鉤子函數)有何不同呢?

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。

而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK(相當於FAR PASCAL),這主要是說明該函數的調用方式。

至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

也可以這樣,更容易理解:回調函數就好像是一箇中斷處理函數,系統在符合你設定的條件時自動調用。爲此,你需要做三件事:

1.       聲明;

2.       定義;

3.       設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。

聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統,不要把它當作你的某個類的成員函數。


二,回調函數、消息和事件例程


    調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件。

    此調用機制並非完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並非如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

    回調函數使程序結構亂了許多。Windows API 函數集中有不少回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。

無論何種事物,能以樹形結構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜往往需要構成網狀結構,非簡單的客戶/服務器關係能窮盡。

    Windows 系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的 WParam 和 LParam 相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows 發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程 B 收到進程 A 發來的消息,啓動了一段代碼,其中又向進程 A 發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

    利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口 handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借 API 函數 SendMessage 向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借 API 函數 PostMessage 發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

    如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。

    不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但並不會使程序減速。
(例程似乎是進程的臺灣翻譯。)


三,精妙比喻:回調函數還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

       回調用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調,例如作爲一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。其實回調和API非常接近,他們的共性都是跨層調用的函數。但區別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來保存這個回調,在需要調用時,只需引用這個函數指針和相關的參數指針。    其實:回調就是該函數寫在高層,低層通過一個函數指針保存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數。



    軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎。
   
    對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。

    對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。

    在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。

    Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。

    對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。

    下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。

    2 過程語言中的回調(C)


    2.1 函數指針
    回調在C語言中是通過函數指針來實現的,通過將回調函數的地址傳給被調函數從而實現回調。因此,要實現回調,必須首先定義函數指針,請看下面的例子:

    void Func(char *s);// 函數原型
    void (*pFunc) (char *);//函數指針

    可以看出,函數的定義和函數指針的定義非常類似。

    一般的化,爲了簡化函數指針類型的變量定義,提高程序的可讀性,我們需要把函數指針類型自定義一下。

    typedef void(*pcb)(char *);

    回調函數可以象普通函數一樣被程序調用,但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。

    被調函數的例子:

    void GetCallBack(pcb callback)
    {
    /*do something*/
    }
    用戶在調用上面的函數時,需要自己實現一個pcb類型的回調函數:
    void fCallback(char *s)
    {
    /* do something */
    }
    然後,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
    GetCallBack(fCallback);

    如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。

    2.2 參數傳遞規則
    到目前爲止,我們只討論了函數指針及回調而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種調用規範。如在Visual C++中,可以在函數類型前加_cdecl,_stdcall或者_pascal來表示其調用規範(默認爲_cdecl)。C++ Builder也支持_fastcall調用規範。調用規範影響編譯器產生的給定函數名,參數傳遞的順序(從右到左或從左到右),堆棧清理責任(調用者或者被調用者)以及參數傳遞機制(堆棧,CPU寄存器等)。

    將調用規範看成是函數類型的一部分是很重要的;不能用不兼容的調用規範將地址賦值給函數指針。例如:

    // 被調用函數是以int爲參數,以int爲返回值
    __stdcall int callee(int);

    // 調用函數以函數指針爲參數
    void caller( __cdecl int(*ptr)(int));

    // 在p中企圖存儲被調用函數地址的非法操作
    __cdecl int(*p)(int) = callee; // 出錯

    指針p和callee()的類型不兼容,因爲它們有不同的調用規範。因此不能將被調用者的地址賦值給指針p,儘管兩者有相同的返回值和參數列

    2.3 應用舉例
    C語言的標準庫函數中很多地方就採用了回調函數來讓用戶定製處理過程。如常用的快速排序函數、二分搜索函數等。

    快速排序函數原型:

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
    二分搜索函數原型:
    void *bsearch(const void *key, const void *base, size_t nelem,
    size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    其中fcmp就是一個回調函數的變量。

    下面給出一個具體的例子:

    #include <stdio.h>
    #include <stdlib.h>

    int sort_function( const void *a, const void *b);
    int list[5] = { 54, 21, 11, 67, 22 };

    int main(void)
    {
 int x;

 qsort((void *)list, 5, sizeof(list[0]), sort_function);
 for (x = 0; x < 5; x++)
 printf("%i/n", list[x]);
 return 0;
 }

 int sort_function( const void *a, const void *b)
 {
 return *(int*)a-*(int*)b;
 }

 2.4 面嚮對象語言中的回調(Delphi)

 Dephi與C++一樣,爲了保持與過程語言Pascal的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。

 

簡單地說:被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調
找了點回調函數的東西

序言

看完下面的專家解答你就明白了

Q :
編程工具: C++ BUILDER 3.0
操作系統: WIN98
我想在C++ 中使用回調函數,請問它的內在機制如何,另外怎麼定義。我用DialogBox函數時,如何使用回調函數? 它和鉤子函數有何不同?多謝指教!!!拜託!!!

A:

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK,這主要是說明該函數的調用方式。DialogBox的回調函數實際上是個窗口過程,用來處理所有消息。其定義爲:
BOOL CALLBACK DialogProc(

HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有詳細說明。一般使用C++ Builder或MFC的往往沒有使用SDK編程的經驗,建議找一些SDK編程的書看一下,否則很難理解如何使用窗口過程。
至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

frank的意見:
我對回調函數的理解雖然粗淺,但是我覺得會讓人更容易理解:回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。
聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統。不要把它當作你的某個類的成員函數。

ping的意見:
frank說:回調函數屬於WINDOWS系統。我覺得不應該說回調函數是屬於系統的。應該說是程序把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因爲消息是系統一手掌握着的,由系統來調用我們的程序對消息的處理部分,這樣子會比較方便。不然我們又得花力氣去讀消息列表了。

簡單的話:你來定義,系統調用.系統自有辦法.

正文


一,回調函數

我們經常在C++設計時通過使用回調函數可以使有些應用(如定時器事件回調處理、用回調函數記錄某操作進度等)變得非常方便和符合邏輯,那麼它的內在機制如何呢,怎麼定義呢?它和其它函數(比如鉤子函數)有何不同呢?

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。

而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK(相當於FAR PASCAL),這主要是說明該函數的調用方式。

至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

也可以這樣,更容易理解:回調函數就好像是一箇中斷處理函數,系統在符合你設定的條件時自動調用。爲此,你需要做三件事:

1.       聲明;

2.       定義;

3.       設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。

聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統,不要把它當作你的某個類的成員函數。


二,回調函數、消息和事件例程


    調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件。

    此調用機制並非完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並非如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

    回調函數使程序結構亂了許多。Windows API 函數集中有不少回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。

無論何種事物,能以樹形結構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜往往需要構成網狀結構,非簡單的客戶/服務器關係能窮盡。

    Windows 系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的 WParam 和 LParam 相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows 發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程 B 收到進程 A 發來的消息,啓動了一段代碼,其中又向進程 A 發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

    利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口 handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借 API 函數 SendMessage 向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借 API 函數 PostMessage 發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

    如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。

    不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但並不會使程序減速。
(例程似乎是進程的臺灣翻譯。)


三,精妙比喻:回調函數還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

       回調用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調,例如作爲一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。其實回調和API非常接近,他們的共性都是跨層調用的函數。但區別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來保存這個回調,在需要調用時,只需引用這個函數指針和相關的參數指針。    其實:回調就是該函數寫在高層,低層通過一個函數指針保存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數。



    軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎。
   
    對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。

    對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。

    在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。

    Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。

    對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。

    下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。

    2 過程語言中的回調(C)


    2.1 函數指針
    回調在C語言中是通過函數指針來實現的,通過將回調函數的地址傳給被調函數從而實現回調。因此,要實現回調,必須首先定義函數指針,請看下面的例子:

    void Func(char *s);// 函數原型
    void (*pFunc) (char *);//函數指針

    可以看出,函數的定義和函數指針的定義非常類似。

    一般的化,爲了簡化函數指針類型的變量定義,提高程序的可讀性,我們需要把函數指針類型自定義一下。

    typedef void(*pcb)(char *);

    回調函數可以象普通函數一樣被程序調用,但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。

    被調函數的例子:

    void GetCallBack(pcb callback)
    {
    /*do something*/
    }
    用戶在調用上面的函數時,需要自己實現一個pcb類型的回調函數:
    void fCallback(char *s)
    {
    /* do something */
    }
    然後,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
    GetCallBack(fCallback);

    如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。

    2.2 參數傳遞規則
    到目前爲止,我們只討論了函數指針及回調而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種調用規範。如在Visual C++中,可以在函數類型前加_cdecl,_stdcall或者_pascal來表示其調用規範(默認爲_cdecl)。C++ Builder也支持_fastcall調用規範。調用規範影響編譯器產生的給定函數名,參數傳遞的順序(從右到左或從左到右),堆棧清理責任(調用者或者被調用者)以及參數傳遞機制(堆棧,CPU寄存器等)。

    將調用規範看成是函數類型的一部分是很重要的;不能用不兼容的調用規範將地址賦值給函數指針。例如:

    // 被調用函數是以int爲參數,以int爲返回值
    __stdcall int callee(int);

    // 調用函數以函數指針爲參數
    void caller( __cdecl int(*ptr)(int));

    // 在p中企圖存儲被調用函數地址的非法操作
    __cdecl int(*p)(int) = callee; // 出錯

    指針p和callee()的類型不兼容,因爲它們有不同的調用規範。因此不能將被調用者的地址賦值給指針p,儘管兩者有相同的返回值和參數列

    2.3 應用舉例
    C語言的標準庫函數中很多地方就採用了回調函數來讓用戶定製處理過程。如常用的快速排序函數、二分搜索函數等。

    快速排序函數原型:

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
    二分搜索函數原型:
    void *bsearch(const void *key, const void *base, size_t nelem,
    size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    其中fcmp就是一個回調函數的變量。

    下面給出一個具體的例子:

    #include <stdio.h>
    #include <stdlib.h>

    int sort_function( const void *a, const void *b);
    int list[5] = { 54, 21, 11, 67, 22 };

    int main(void)
    {
 int x;

 qsort((void *)list, 5, sizeof(list[0]), sort_function);
 for (x = 0; x < 5; x++)
 printf("%i/n", list[x]);
 return 0;
 }

 int sort_function( const void *a, const void *b)
 {
 return *(int*)a-*(int*)b;
 }

 2.4 面嚮對象語言中的回調(Delphi)

 Dephi與C++一樣,爲了保持與過程語言Pascal的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。

 

簡單地說:被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調
找了點回調函數的東西

序言

看完下面的專家解答你就明白了

Q :
編程工具: C++ BUILDER 3.0
操作系統: WIN98
我想在C++ 中使用回調函數,請問它的內在機制如何,另外怎麼定義。我用DialogBox函數時,如何使用回調函數? 它和鉤子函數有何不同?多謝指教!!!拜託!!!

A:

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK,這主要是說明該函數的調用方式。DialogBox的回調函數實際上是個窗口過程,用來處理所有消息。其定義爲:
BOOL CALLBACK DialogProc(

HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有詳細說明。一般使用C++ Builder或MFC的往往沒有使用SDK編程的經驗,建議找一些SDK編程的書看一下,否則很難理解如何使用窗口過程。
至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

frank的意見:
我對回調函數的理解雖然粗淺,但是我覺得會讓人更容易理解:回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。
聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統。不要把它當作你的某個類的成員函數。

ping的意見:
frank說:回調函數屬於WINDOWS系統。我覺得不應該說回調函數是屬於系統的。應該說是程序把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因爲消息是系統一手掌握着的,由系統來調用我們的程序對消息的處理部分,這樣子會比較方便。不然我們又得花力氣去讀消息列表了。

簡單的話:你來定義,系統調用.系統自有辦法.

正文


一,回調函數

我們經常在C++設計時通過使用回調函數可以使有些應用(如定時器事件回調處理、用回調函數記錄某操作進度等)變得非常方便和符合邏輯,那麼它的內在機制如何呢,怎麼定義呢?它和其它函數(比如鉤子函數)有何不同呢?

使用回調函數實際上就是在調用某個函數(通常是API函數)時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個函數。

而那個函數在需要的時候,利用傳遞的地址調用回調函數,這時你可以利用這個機會在回調函數中處理消息或完成一定的操作。至於如何定義回調函數,跟具體使用的API函數有關,一般在幫助中有說明回調函數的參數和返回值等。C++中一般要求在回調函數前加CALLBACK(相當於FAR PASCAL),這主要是說明該函數的調用方式。

至於鉤子函數,只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一起使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。

也可以這樣,更容易理解:回調函數就好像是一箇中斷處理函數,系統在符合你設定的條件時自動調用。爲此,你需要做三件事:

1.       聲明;

2.       定義;

3.       設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用。

聲明和定義時應注意:回調函數由系統調用,所以可以認爲它屬於WINDOWS系統,不要把它當作你的某個類的成員函數。


二,回調函數、消息和事件例程


    調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件。

    此調用機制並非完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並非如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

    回調函數使程序結構亂了許多。Windows API 函數集中有不少回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。

無論何種事物,能以樹形結構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜往往需要構成網狀結構,非簡單的客戶/服務器關係能窮盡。

    Windows 系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的 WParam 和 LParam 相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows 發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程 B 收到進程 A 發來的消息,啓動了一段代碼,其中又向進程 A 發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

    利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口 handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借 API 函數 SendMessage 向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借 API 函數 PostMessage 發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

    如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。

    不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但並不會使程序減速。
(例程似乎是進程的臺灣翻譯。)


三,精妙比喻:回調函數還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

       回調用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調,例如作爲一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。其實回調和API非常接近,他們的共性都是跨層調用的函數。但區別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來保存這個回調,在需要調用時,只需引用這個函數指針和相關的參數指針。    其實:回調就是該函數寫在高層,低層通過一個函數指針保存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數。



    軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎。
   
    對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。

    對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。

    在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。

    Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。

    對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。

    下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。

    2 過程語言中的回調(C)


    2.1 函數指針
    回調在C語言中是通過函數指針來實現的,通過將回調函數的地址傳給被調函數從而實現回調。因此,要實現回調,必須首先定義函數指針,請看下面的例子:

    void Func(char *s);// 函數原型
    void (*pFunc) (char *);//函數指針

    可以看出,函數的定義和函數指針的定義非常類似。

    一般的化,爲了簡化函數指針類型的變量定義,提高程序的可讀性,我們需要把函數指針類型自定義一下。

    typedef void(*pcb)(char *);

    回調函數可以象普通函數一樣被程序調用,但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。

    被調函數的例子:

    void GetCallBack(pcb callback)
    {
    /*do something*/
    }
    用戶在調用上面的函數時,需要自己實現一個pcb類型的回調函數:
    void fCallback(char *s)
    {
    /* do something */
    }
    然後,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
    GetCallBack(fCallback);

    如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。

    2.2 參數傳遞規則
    到目前爲止,我們只討論了函數指針及回調而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種調用規範。如在Visual C++中,可以在函數類型前加_cdecl,_stdcall或者_pascal來表示其調用規範(默認爲_cdecl)。C++ Builder也支持_fastcall調用規範。調用規範影響編譯器產生的給定函數名,參數傳遞的順序(從右到左或從左到右),堆棧清理責任(調用者或者被調用者)以及參數傳遞機制(堆棧,CPU寄存器等)。

    將調用規範看成是函數類型的一部分是很重要的;不能用不兼容的調用規範將地址賦值給函數指針。例如:

    // 被調用函數是以int爲參數,以int爲返回值
    __stdcall int callee(int);

    // 調用函數以函數指針爲參數
    void caller( __cdecl int(*ptr)(int));

    // 在p中企圖存儲被調用函數地址的非法操作
    __cdecl int(*p)(int) = callee; // 出錯

    指針p和callee()的類型不兼容,因爲它們有不同的調用規範。因此不能將被調用者的地址賦值給指針p,儘管兩者有相同的返回值和參數列

    2.3 應用舉例
    C語言的標準庫函數中很多地方就採用了回調函數來讓用戶定製處理過程。如常用的快速排序函數、二分搜索函數等。

    快速排序函數原型:

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
    二分搜索函數原型:
    void *bsearch(const void *key, const void *base, size_t nelem,
    size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    其中fcmp就是一個回調函數的變量。

    下面給出一個具體的例子:

    #include <stdio.h>
    #include <stdlib.h>

    int sort_function( const void *a, const void *b);
    int list[5] = { 54, 21, 11, 67, 22 };

    int main(void)
    {
 int x;

 qsort((void *)list, 5, sizeof(list[0]), sort_function);
 for (x = 0; x < 5; x++)
 printf("%i/n", list[x]);
 return 0;
 }

 int sort_function( const void *a, const void *b)
 {
 return *(int*)a-*(int*)b;
 }

 2.4 面嚮對象語言中的回調(Delphi)

 Dephi與C++一樣,爲了保持與過程語言Pascal的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。

 

發佈了5 篇原創文章 · 獲贊 9 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章