天天看點

何為CallBack???

遺忘的垃圾 /鬼童  發表于2005-04-17

前天一個朋友問我>>>>>>

回調函數是怎麼一會事兒?讓我給她解釋一下,一時間我也不知道應該如何作答,就告訴她, 回調就是自己定義的函數但是由系統來調用,比如說window proc,thread proc function,但是 她說這些都知道,就是不明白為什麼要定義CallBack,CallBack這個KeyWord究竟有什麼意義? 完了,這麼較汁的問題我也傻了眼。

第二天來到公司把CallBack一頓Google+Baidu,找到了一些雖然我明白但是卻解釋不清的東西:

回調用于層間協作,上層将本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調, 例如作為一個驅動,是一個底層,他在收到一個資料時,除了完成本層的處理工作外,還将進行回調, 将這個資料交給上層應用層來做進一步處理,這在分層的資料通信中很普遍。

其實回調和API非常接近,他們的共性都是跨層調用的函數。但差別是API是低層提供給高層的調用,一般這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對于低層他是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它通過一個函數指針來儲存這個回調,在需要調用時,隻需引用這個函數指針和相關的參數指針。 回調就是該函數寫在高層,低層通過一個函數指針儲存這個函數,在某個事件的觸發下,低層通過該函數指針調用高層那個函數

回調函數是一個很有用,也很重要的概念。當發生某種事件時,系統或其他函數将會自動調用你定義的一段函數。回調函數在windows程式設計使用的場合很多,比如Hook回調函數:

MouseProc,GetMsgProc以及EnumWindows,DrawState的回調函數等等,還有很多系統級的回調過程。

一:聲明回調函數類型。 typedef int (WINAPI*PFCALLBACK)(intParam1,intParam2); 實際上是聲明了一個傳回值為int,傳入參數為兩個int的指向函數的指針。由于C++和PASCAL編譯器對參數入棧和函數傳回的處理有可能不一緻,把函數類型用WINAPI(WINAPI宏展開就是__stdcall)或stdcall統一修飾。

二:聲明回調函數原形  聲明函數原形

int WINAPICBFunc(intParam1,intParam2);

 以上函數為全局函數,如果要使用一個類裡的函數作為回調函數原形,把該類函數聲明為靜态函數即可。

三:回調函數調用調用者  調用回調函數的函數我把它放到了DLL裡,這是一個很簡單的VC生成的WIN32DLL.并使用DEF檔案輸出其函數名TestCallBack。實作如下:  PFCALLBACK gCallBack=0;

 void WINAPI TestCallBack(PFCALLBACKFunc)

 {

  if(Func==NULL) return;

  gCallBack=Func;

  DWORDThreadID=0;

  HANDLEhThread=CreateThread(NULL,NULL,Thread1,LPVOID(0),ThreadID);

  return;

 }

 此函數的工作把傳入的PFCALLBACKFunc參數儲存起來等待使用,并且啟動一個線程。聲明了一個函數指針PFCALLBACKgCallBack儲存傳入的函數位址。 回調函數就相當于一個中斷處理函數,由系統在符合你設定的條件時自動調用。為此,你需要做三件事:1,聲明;2,定義;3,設定觸發條件,就是在你的函數中把你的回調函數名稱轉化為位址作為一個參數,以便于系統調用。聲明和定義時應注意:回調函數由系統調用,是以可以認為它屬于WINDOWS系統。不要把它當作你的某個類的成員函數。

回調函數屬于WINDOWS系統。我覺得不應該說回調函數是屬于系統的。應該說是程式把這段代碼的觸發交由系統來做。而這種做法是WINDOWS提供的處理機制吧,因為消息是系統一手掌握着的,由系統來調用我們的程式對消息的處理部分,這樣子會比較友善。不然我們又得花力氣去讀消息清單了。

軟體子產品之間總是存在着一定的接口,從調用方式上,

可以把他們分為三類:同步調用、回調和異步調用

1 什麼是回調

  軟體子產品之間總是存在着一定的接口,從調用方式上,可以把他們分為三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才傳回, 它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關系非常緊密,通常我們使用回調來實作異步消息的注冊,通過異步調用來實作消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎,是以,下面我們着重讨論回調機制在不同軟體架構中的實作。

何為CallBack???

 對于不同類型的語言(如結構化語言和對象語言)、平台(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的互動除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調是實作異步的一個最簡捷的途徑。

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

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

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

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

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

2.1 函數指針

回調在C語言中是通過函數指針來實作的,通過将回調函數的位址傳給被調函數進而實作回調。是以,要實作回調,必須首先定義函數指針,請看下面的例子:

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

void (*pFunc) (char *);//函數指針

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

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

typedef void(*pcb)(char *);

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

被調函數的例子:

void GetCallBack(pcb callback)

{

 

}

  使用者在調用上面的函數時,需要自己實作一個pcb類型的回調函數:

void fCallback(char *s)

{

}

  然後,就可以直接把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("%in", list[x]);

  return 0;

}

int sort_function( const void *a, const void *b)

{

 return *(int*)a-*(int*)b;

}

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

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

  2.4.1 回調函數

  回調函數類型定義:

type

TCalcFunc=function (a:integer;b:integer):integer;

  按照回調函數的格式自定義函數的實作,如

function Add(a:integer;b:integer):integer

begin

 result:=a+b;

end;

function Sub(a:integer;b:integer):integer

begin

 result:=a-b;

end;

  回調的使用

function Calc(calc:TcalcFunc;a:integer;b:integer):integer

  下面,我們就可以在我們的程式裡按照需要調用這兩個函數了

c:=calc(add,a,b);//c=a+b

c:=calc(sub,a,b);//c=a-b

2.4.2 回調對象

  什麼叫回調對象呢,它具體用在哪些場合?首先,讓我們把它與回調函數對比一下,回調函數是一個定義了函數的原型,函數體則交由第三方來實作的一種動态應用模式。要實作一個回調函數,我們必須明确知道幾點:該函數需要那些參數,傳回什麼類型的值。同樣,一個回調對象也是一個定義了對象接口,但是沒有具體實作的抽象類(即接口)。要實作一個回調對象,我們必須知道:它需要實作哪些方法,每個方法中有哪些參數,該方法需要放回什麼值。

  是以,在回調對象這種應用模式中,我們會用到接口。接口可以了解成一個定義好了但是沒有實作的類,它隻能通過繼承的方式被别的類實作。Delphi中的接口和COM接口類似,所有的接口都繼承與IInterface(等同于IUnknow),并且要實作三個基本的方法QueryInterface, _AddRef, 和_Release。

  定義一個接口

type IShape=interface(IInterface)

procedure Draw;

end

  實作回調類

type TRect=class(TObject,IShape)

protected

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

public

procedure Draw;

end;

type TRound=class(TObject,IShape)

protected

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

public

procedure Draw;

end;

  使用回調對象

procedure MyDraw(shape:IShape);

var

shape:IShape;

begin

shape.Draw;

end;

  如果傳入的對象為TRect,那麼畫矩形;如果為TRound,那麼就為圓形。使用者也可以按照自己的意圖來實作IShape接口,畫出自己的圖形:

MyDraw(Trect.Create);

MyDraw(Tround.Create);

  2.4.3 回調方法

  回調方法(Callback Method)可以看作是回調對象的一部分,Delphi對windows消息的封裝就采用了回調方法這個概念。在有些場合,我們不需要按照給定的要求實作整個對象,而隻要實作其中的一個方法就可以了,這是我們就會用到回調方法。

  回調方法的定義如下:

TNotifyEvent = procedure(Sender: TObject) of object;

TMyEvent=procedure(Sender:Tobject;EventId:Integer) of object;

  TNotifyEvent 是Delphi中最常用的回調方法,窗體、控件的很多事件,如單擊事件、關閉事件等都是采用了TnotifyEvent。回調方法的變量一般通過事件屬性的方式來定義,如TCustomForm的建立事件的定義:

property OnCreate: TNotifyEvent read FOnCreate write FOnCreate stored IsForm;

  我們通過給事件屬性變量指派就可以定制事件處理器。

  使用者定義對象(包含回調方法的對象):

type TCallback=Class

procedure ClickFunc(sender:TObject);

end;

procedure Tcallback.ClickFunc(sender:TObject);

begin

showmessage('the caller is clicked!');

end;

  窗體對象:

type TCustomFrm=class(TForm)

public

procedure RegisterClickFunc(cb:procedure(sender:Tobject) of object);

end;

procedure TcustomFrm..RegisterClickFunc(cb:TNotifyEvent);

begin

self.OnClick=cb;

end;

  使用方法:

var

frm:TcustomFrm;

begin

frm:=TcustomFrm.Create(Application);

frm.RegisterClickFunc(Tcallback.Create().ClickFunc);

end;

回調函數必須有關鍵詞 CALLBACK回調函數本身必須是全局函數或者靜态函數,不可定義為某個特定的類的成員函數?

2 回調函數并不由開發者直接調用執行(隻是使用系統接口API函數作為起點)

3 回調函數通常作為參數傳遞給系統API,由該API來調用

4 回調函數可能被系統API調用一次,也可能被循環調用多次(SortItem就是自調用)

其實windows系統中還有另一種機制-消息機制,也是一個比較不錯的工具,能夠為很多實際的問題提供解決方法。

就這些了, 如果她還問我CallBack是什麼意思,那隻有MSDN上的最終答案:

CALLBACK : Calling convention for callback functions.

 src address: http://www.bloghome.cn/posts/2313

繼續閱讀