??? 模塊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;?????????????
???????以上函數為全局函數,如果要使用一個類里的函數作為回調函數原形,把該類函數聲明為靜態函數即可。
三:?回調函數調用調用者
??????????調用回調函數的函數我把它放到了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;
?? DWORD?StartTick;
?????????//一個延時循環
??????????????for(;Step<200;Step++)
??????????????{
?????????????????????????StartTick?=?GetTickCount();
???????????????????/*這一段為線程交出部分運行時間以讓系統處理其他事務*/
????????????????????????for(;GetTickCount()-StartTick<10;)
??????????????????????????{
??????????????????????????????????if(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE)?)
??????????????????????????????????{
????????????????????????????????????TranslateMessage(&Msg);
????????????????????????????????????DispatchMessage(&Msg);
????????????????????????????????????}
????????????????????????????}??
???????????????????????/*把運行情況打印到桌面,這是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);
?????????????MessageBox(NULL,Buffer,"Testing",MB_OK);???//演示回調函數被調用
??????????????return?res;?
?}???
??????????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里的多線程延時后調用回調函數的方式,只是為了突出一下回調函數的效果,其實只要是在本進程之內,都可以隨你高興可以把函數地址傳遞來傳遞去,當成回調函數使用。
????????這樣的編程模式原理非常簡單單一:就是把函數也看成一個指針一個地址來調用,沒有什么別的復雜的東西,僅僅是編程里的一個小技巧。至于回調函數模式究竟能為你帶來多少好處,就看你是否使用,如何使用這種編程模式了。
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?文檔。
我談一下自己對回調函數的一點理解,?不對的地方請指教.
?????我剛開始接觸回調時,?也是一團霧水.很多人解釋這個問題時,?總是拿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???,?其實也相當于全局的.