回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:
|
可以看出,函数的定义和函数指针的定义非常类似。
一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。
|
回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。
被调函数的例子:
|
如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
2 参数传递规则
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
|
指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列
3 回调函数实例:
ListCtrl控件的SortItem方法:
(1)函数指针的声明:CommCtrl.h
1 typedef int (CALLBACK *PFNLVCOMPARE)(LPARAM, LPARAM, LPARAM);
(2)函数的声明:afxcmn.h
1 BOOL SortItems(_In_ PFNLVCOMPARE pfnCompare, _In_ DWORD_PTR dwData);
(3)函数的实现:afxcmn.inl
1 _AFXCMN_INLINE BOOL CListCtrl::SortItems(PFNLVCOMPARE pfnCompare, DWORD_PTR dwData) 2 { 3 4 ASSERT(::IsWindow(m_hWnd)); 5 6 ASSERT((GetStyle() & LVS_OWNERDATA)==0); 7 8 return (BOOL) ::SendMessage(m_hWnd, LVM_SORTITEMS, dwData, (LPARAM)pfnCompare); 9 10 }
(4)回调函数的实现:返回类型与参数列表与函数指针保持一致
1 int CALLBACK MylistCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) 2 3 { 4 5 CString * pstrTemp = (CString*)lParamSort; 6 7 ............................................................. 8 }
(5)回调函数的使用
1 void CMFC_TestDlg::OnLvnColumnclickList2(NMHDR *pNMHDR, LRESULT *pResult) 2 { 3 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR); 4 5 CString strTemp; 6 ListControl.SortItems(MylistCompare, (LPARAM)&strTemp); 7 8 }