軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分爲三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關係非常緊密,通常我們使用回調來實現異步消息的註冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎,因此,下面我們着重討論回調機制在不同軟件架構中的實現。
< A-->B;A-->B-->A;A<--B; >示例
對於不同類型的語言(如結構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實現異步的一個最簡捷的途徑。
對於一般的結構化語言,可以通過回調函數來實現回調。回調函數也是一個函數或過程,不過它是一個由調用方自己實現,供被調用方使用的特殊函數。
在面向對象的語言中,回調則是通過接口或抽象類來實現的,我們把實現這種接口的類成爲回調類,回調類的對象成爲回調對象。對於象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調對象、回調方法等特性,也能兼容過程語言的回調函數機制。
Windows平臺的消息機制也可以看作是回調的一種應用,我們通過系統提供的接口註冊消息處理函數(即回調函數),從而實現接收、處理消息的目的。由於Windows平臺的API是用C語言來構建的,我們可以認爲它也是回調函數的一個特例。
對於分佈式組件代理體系CORBA,異步處理有多種方式,如回調、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發、維護等工作。對一些簡單的異步處理過程,我們可以通過回調機制來實現。
下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體作用等。
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); |
如果賦了不同的值給該參數,那麼調用者將調用不同地址的函數。賦值可以發生在運行時,這樣使你能實現動態綁定。
到目前爲止,我們只討論了函數指針及回調而沒有去注意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,儘管兩者有相同的返回值和參數列
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的兼容性,它在引入面向對象機制的同時,保留了以前的結構化特性。因此,對回調的實現,也有兩種截然不同的模式,一種是結構化的函數回調模式,一種是面向對象的接口模式。 什麼叫回調對象呢,它具體用在哪些場合?首先,讓我們把它與回調函數對比一下,回調函數是一個定義了函數的原型,函數體則交由第三方來實現的一種動態應用模式。要實現一個回調函數,我們必須明確知道幾點:該函數需要那些參數,返回什麼類型的值。同樣,一個回調對象也是一個定義了對象接口,但是沒有具體實現的抽象類(即接口)。要實現一個回調對象,我們必須知道:它需要實現哪些方法,每個方法中有哪些參數,該方法需要放回什麼值。
|
陳家朋,系統架構師和技術顧問,目前擔任杭州邁可行通信技術有限公司MPS2000業務交換平臺的首席設計人員。擅長架構設計、提供技術解決方案等工作,熟知計算機和通信領域的各種前沿技術。可以通過 [email protected]跟他聯繫。 http://www.ibm.com/developerworks/cn/linux/l-callback/index.html |