如下內容對原創做品,進行了加工處理,在此感謝原創奉獻,有了大家的靈感,纔會引發更多志者共鳴:架構
使用回調函數實際上就是在調用某個函數(一般是API函數)時,將本身的一個函數(這個函數爲回調函數)的地址做爲參數傳遞給那個函數。而那個函數在須要的時候,利用傳遞的地址調用回調函數,這時你能夠利用這個機會在回調函數中處理消息或完成必定的操做。(這。。這。。這回調函數咋就越看越像一臥底,還赤裸裸地把函數形態暴露給人家底層,人家底層還得用它作參數包裝成一個API接口再讓給你用,你丫的用人家底層的接口不說,還派個臥底過去搗亂或監督;不過你彆着急,大多時候底層仍是很喜歡這個臥底,並且常常主動提出這個臥底方案,很好地解決了雙方的矛盾衝突)函數
回調函數還與Hook函數相相似,Hook函數只是回調函數的一個特例。習慣上把與SetWindowsHookEx函數一塊兒使用的回調函數稱爲鉤子函數。也有人把利用VirtualQueryEx安裝的函數稱爲鉤子函數,不過這種叫法不太流行。ui
下面是一個動態庫與應用程序之間,實現回調函數的例子,重在說明回調函數的實現機制,至於內部代碼,根據本身程序須要來更改.spa
步驟:線程
一、在動態庫中:指針
1.1 聲明應用程序中回調函數的原形,例子以下:(知道臥底身份還得接歸入隊,5555)調試
typedef int (WINAPI *PFCALLBACK)(int Param1, int Param2);code
1.2 定義回調函數類型:排序
PFCALLBACK gCallBack = 0;接口
1.3 寫被應用程序調用的接口函數:(雙方對接接口,並且這一接口是底層和上層的分水嶺,回調函數實現方屬於上層,提供此接口者屬於底層)
(到這裏咱們應該知道2件事:
(1)這一步中的接口函數就是底層提供給上層的API接口,他們之間遵循API規範,好比上層應該知道底層API接口名稱進而能夠獲取API地址;
(2)雙方還有統一的回調函數聲明(見第1.1步),但二者做用是大大不一樣:
底層說:按照雙方正常的對接API接口,我把這個API交給你上層了。但這個API不一樣於普通的API,它是帶了一個函數指針的(專門作函數的參數的函數亦稱之爲回調函數),這個回調函數是你特殊定製的,我只負責聲明和調用,至於它具體的內容是什麼只有你知道。。也就是說:我這裏只負責管我API的內容和調用回調函數。
extern "C" void WINAPI TestCallBack(PFCALLBACK Func)
{
if(Func == NULL)
return;
gCallBack = Func;
DWORD ThreadID = 0;
HANDLE hThread = CreateThread(NULL, 0, Thread1, LPVOID(0), 0, &ThreadID);
return;
}
1.4 寫Thread1這個過程:(內部調用接口)
DWORD WINAPI Thread1(
LPVOID lpParameter // thread data
)
{
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;
}
二、在應用程序中:
2.1 在頭文件中,聲明回調函數原形,並定義應用程序實例句柄:(臥底原型聲明)
typedef int (WINAPI *PFCALLBACK)(int Param1, int Param2);
HINSTANCE m_handle;
2.2 在執行文件中加載動態庫:(加載第3方底層庫)
m_handle = LoadLibrary("CallBack_Dll.dll");
2.3 在一個按鈕事件中,調用動態庫接口函數:(調用底層接口,自家兄弟(臥底)就藏在接口裏)
void CVC_CallBackDlg::OnCallBack()
{
// TODO: Add your control notification handler code here
typedef void (WINAPI *PTestCallBack)(PFCALLBACK);
PTestCallBack addFun; //函數指針
addFun = (PTestCallBack)GetProcAddress(m_handle, "TestCallBack");
addFun(CBFunc);
}
2.4 寫回調函數過程:(部署臥底的做戰任務)
int WINAPI CBFunc(int Param1, int Param2)
{
int res = Param1 + Param2;
CHAR Buffer[256] = "";
sprintf(Buffer, "callback result = %d", res);
::MessageBox(NULL, Buffer, "Test", MB_OK); //演示回調函數被調用
return res;
}
2.5 在窗口退出時,釋放應用程序實例句柄:
void CVC_CallBackDlg::OnCancel()
{
// TODO: Add extra cleanup here
FreeLibrary(m_handle);
CDialog::OnCancel();
}
至此,動態庫與應用程序的整個回調函數實現過程,就實現了。
其它網摘:
回調函數:
使用回調函數實際上就是在調用某個函數(一般是API函數)時,將本身的一個函數(這個函數爲回調函數)的地址做爲參數傳遞給那個函數。而那個函數在須要的時候,利用傳遞的地址調用回調函數,這時你能夠利用這個機會在回調函數中處理消息或完成必定的操做。
回調函數,顧名思義,就是使用者本身定義一個函數,使用者本身實現這個函數的程序內容, 而後把這個函數做爲參數傳入別人(或系統)的函數中,由別人(或系統)的函數在運行時來調用的函數。函數是你實現的,但由別人(或系統)的函數在運行時通 過參數傳遞的方式調用,這就是所謂的回調函數。簡單來講,就是由別人的函數運行期間來回調你實現的函數。
回調函數能夠象普通函數同樣被程序調用,可是隻有它被看成參數傳遞給被調函數時才能稱做回調函數。
精妙比喻:
回調函數有點像隨身帶的BP機:告訴別人號碼,在它有事情時Call您。
回調用於層間協做,上層將本層函數安裝在下層,這個函數就是回調,而下層在必定條件下觸發回調。例如做爲一個驅動,是一個底層,他收到一個數據時,除了完成本層的處理工做外,還將進行回調,將這個數據交給上層應用層來作進一步處理,這個分層的數據通訊中很廣泛。
其實回調和API很是接近,他們的共性者是跨層調用的函數。但區別是API是低層提供給高層的調用,通常這個函數對高層都是已知的;而回調正好相反,他是高層提供給底層的調用,對於低層是未知的,必須由高層進行安裝,這個安裝函數其實就是一個低層提供的API,安裝後低層不知道這個回調的名字,但它經過一個函數指針來保存這個回調,在須要調用時,只須要引用這個函數指針和相關的參數指針。其實:回調就是該函數寫在高層,低層經過一個函數指針保存這個函數,在某個事件的觸發下,低層經過該函數指針調用高層那個函數。
下面咱們集中比較具備表明性的語言(C、Object Pascal)和架構(CORBA)來分析回調的實現方式、具體做用等。
過程語言中的回調(C)
(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 )參數傳遞規則
到目前爲止,咱們只討論了函數指針及回調而沒有去注意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,儘管二者有相同的返回值和參數列
(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;}