C++回调函数Callback
一、函数指针
回调机制在 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);
二、什么是回调函数
总结上述示例,回调函数就是一个通过函数指针调用的函数。如果你把函数指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
回调函数是继续自C 语言的,因而,在C++中,应只在与C 代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。
三、标准调用约定
到目前为止,我们只讨论了函数指针及回调而没有去注意 ANSI C/C++ 的编译器规范。许多编译器有几种调用约定。
Visual C/C++ 的编译器支持如下的函数调用约定:
关键字 |
清理堆栈 |
参数入栈顺序 |
__cdecl |
调用函数 |
由右至左 |
__stdcall |
被调用函数 |
由右至左 |
__fastcall |
被调用函数 |
由右至左 |
thiscall (非关键字) |
被调用函数 |
由右至左 |
任何支持开发基于 Win32 的程序都必须支持 __stdcall 扩展或其等价物。以__stdcall 标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32 API(除了个别接受可变参数的除外)都使用它,这也是Pascal 的标准约定。与之相对应的是C/C++调用约定,为强制函数使用C/C++调用约定,可使用__cdecl,另外,可变参数函数也使用C/C++调用约定。
Windows 操作系统采用了标准调用约定(Pascal 约定),因为其可减小代码的体积。如果不喜欢__stdcall,还可以使用CALLBACK 宏,它定义在windef.h 中:
#define CALLBACK __stdcallor
#define CALLBACK PASCAL // 而PASCAL 在此被#defined 成__stdcall
四、简单示例
int __stdcall CompareInts(const byte* velem1, const byte* velem2)
{
int elem1 = *(int*)velem1;
int elem2 = *(int*)velem2;
if(elem1 < elem2)
return -1;
if(elem1 > elem2)
return 1;
return 0;
}
int __stdcall CompareStrings(const byte* velem1, const byte* velem2)
{
const char* elem1 = (char*)velem1;
const char* elem2 = (char*)velem2;
return strcmp(elem1, elem2);
}
int main(int argc, char* argv[])
{
int i;
int array[] = {5432, 4321, 3210, 2109, 1098};
cout << "Before sorting ints with Bubblesort\n";
for(i=0; i < 5; i++)
cout << array[i] << '\n';
Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);
cout << "After the sorting\n";
for(i=0; i < 5; i++)
cout << array[i] << '\n';
const char str[5][10] = {"estella","danielle","crissy","bo","angie"};
cout << "Before sorting strings with Quicksort\n";
for(i=0; i < 5; i++)
cout << str[i] << '\n';
Quicksort((byte*)str, 5, 10, &CompareStrings);
cout << "After the sorting\n";
for(i=0; i < 5; i++)
cout << str[i] << '\n';
return 0;
}
五、回调函数的C++封装
使用到C++编写代码,一般会把回调函数写成类中的一个方法,但先来看看以下的代码:
class CCallbackTester
{
public:
int CALLBACK CompareInts(const byte* velem1, const byte* velem2);
};
Bubblesort((byte*)array, 5, sizeof(array[0]), &CCallbackTester::CompareInts);
如果使用微软的编译器,将会得到下面这个编译错误:
error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall
CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const
unsigned char *,const unsigned char *)' There is no context in which this conversion is possible
这是因为非静态成员函数有一个额外的参数:this 指针,这将迫使你在成员函数前面加上static 关键字。
class CCallbackTester
{
public:
static int CALLBACK CompareInts(const byte* velem1, const byte* velem2);
};
静态成员函数虽然很好的解决了this 指针的问题,但是由于静态成员函数只可以调用静态成员函数,这样似乎失去了 C++ 类的大部分优点。那么怎样在静态成员函数中使用类的非静态成员呢?可以给静态成员函数传入参数,而且一般设计较好的回调函数都提供一个数据块指针作为传入参数,这时候可以采用如下的结构来解决此问题。
class CCallbackTester
{
public:
static int CALLBACK CompareInts(void * pData, const byte* velem1, const byte* velem2)
{
CCallbackTester * pThisObject = (CCallbackTester * ) pData;
pThisObject -> CompareInts2(velem1, velem2);
}
void CompareInts2 (const byte* velem1, const byte* velem2)
{
// do something
}
};