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

}

};

参见

  1. Type-safe Callbacks in C++ - CodeProject

  2. 回调函数

  3. 用VC创建新线程易出的问题error C2664: 'CreateThread' : cannot convert parameter 3 from 'unsigned long (void *)' to 'unsigned long (__stdcall

  4. 如何实现类的成员函数创建线程- wangminshe89 - ITeye博客

  5. Using class methods as callbacks - CodeProject