什麼是回撥函式(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]
sprintf(Buffer,/"Running d/",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++回撥函式(callback)的使用
轉載於:https://www.cnblogs.com/zjoch/p/4237197.html
相關文章
- 回撥函式(CallBack)函式
- 函式指標&回撥函式Callback函式指標
- JS之回撥函式(callback)JS函式
- 關於 js 中的回撥函式 callbackJS函式
- 函式回撥(C++)函式C++
- C++中的回撥函式C++函式
- C++回撥函式 用法C++函式
- js 回撥 callbackJS
- 回撥函式函式
- 回撥函式的作用函式
- JavaScript 回撥函式JavaScript函式
- JavaScript回撥函式JavaScript函式
- JS—回撥函式JS函式
- [JS]回撥函式和回撥地獄JS函式
- 回撥函式的理解(一)函式
- C++定義函式指標,回撥C#C++函式指標C#
- 回撥函式 與 函式閉包函式
- java 回撥函式示例Java函式
- Python/OpenCV:回撥函式PythonOpenCV函式
- TLS回撥函式(Note)TLS函式
- React Render Callback Pattern(渲染回撥模式)React模式
- 初步講解JS中的callback回撥原理JS
- 函式指標的重要用途——回撥函式函式指標
- java回撥函式機制Java函式
- java回撥函式-非同步回撥-簡明講解Java函式非同步
- JavaScript基礎——回撥(callback)是什麼JavaScript
- 【知識點】inline函式、回撥函式、普通函式inline函式
- C語言函式指標與回撥函式使用方法C語言函式指標
- 回撥函式的作用與意義函式
- JavaScript回撥函式的高手指南JavaScript函式
- 全網最適合入門的物件導向程式設計教程:47 Python函式方法與介面-回撥函式Callback物件程式設計Python函式
- JavaScript:鉤子函式與回撥函式的區別JavaScript函式
- C++回撥C++
- setInterval 回撥函式傳引數函式
- js 徹底理解回撥函式JS函式
- 深入理解 JavaScript 回撥函式JavaScript函式
- 手把手教你Node使用Promise替代回撥函式Promise函式
- 停止在 React 元件回撥中使用箭頭函式!React元件函式
- C++回撥函式精解:基礎使用和高階技巧一網打盡C++函式