以下内容对原创作品,进行了加工处理,在此感谢原创奉献,有了你们的灵感,才会引起更多志者共鸣:
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。(这。。这。。这回调函数咋就越看越像一卧底,还赤裸裸地把函数形态暴露给人家底层,人家底层还得用它做参数包装成一个API接口再让给你用,你丫的用人家底层的接口不说,还派个卧底过去捣乱或监督;不过你别着急,大多时候底层还是很喜欢这个卧底,而且经常主动提出这个卧底方案,很好地解决了双方的矛盾冲突)
回调函数还与Hook函数相类似,Hook函数只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。
下面是一个动态库与应用程序之间,实现回调函数的例子,重在说明回调函数的实现机制,至于内部代码,根据自己程序需要来更改.
步骤:
1、在动态库中:
1.1 声明应用程序中回调函数的原形,例子如下:(知道卧底身份还得接纳入队,5555)
typedef int (WINAPI *PFCALLBACK)(int Param1, int Param2);
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、在应用程序中:
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;
}