在C/C++中如何实现callback
http://blog.csdn.net/luckheadline
1. callback函数概念
函数指针提供了callback函数的概念。如果你不确信如何使用函数指针,请参见这系列前面的部分。我通过著名的排序函数qsort来介绍callback函数的概念。这个函数将根据用户指定的rank规则对一个域中的元素进行排序。这个域可以包含任何类型的元素;它使用void指针传递给sort函数。元素的大小和元素的数量也要传递给函数。现在的问题是:排序函数在没有元素相关信息的情况下如何排序?答案很简单:函数将接受一个比较函数(有两个void指针元素作为参数)的指针作为参数,得到它们的rank然后返回一个int型结果。因此排序算法需要决定如何rank两个元素,它只是通过函数指针调用比较函数。这就是callback!
2. 在C语言中如何实现callback
这里就拿qsort函数的声明举例:
void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
int(_USERENTRY *cmpFunc)(const void*, const void*));
field指向被排序集合的第一个元素,nElements是集合中元素的个数,sizeOfAnElement是一个元素所占字节个数,cmpFunc是比较函数的函数指针。比较函数有两个void指针类型参数并返回int型。一个callback执行完成就像一个通常的函数执行完成一样。只是使用的是函数指针名而非函数名。
void qsort( ... , int(_USERENTRY *cmpFunc)(const void*, const void*))
{
/* sort algorithm - note: item1 and item2 are void-pointers */
int bigger=cmpFunc(item1, item2); // make callback
/* use the result */
}
3. qsort使用的示例代码
//-----------------------------------------------------------------------------------------
// 3.3 How to make a callback in C by the means of the sort function qsort
#include <stdlib.h> // due to: qsort
#include <time.h> // randomize
#include <stdio.h> // printf
// comparison-function for the sort-algorithm
// two items are taken by void-pointer, converted and compared
int CmpFunc(const void* _a, const void* _b)
{
// you've got to explicitly cast to the correct type
const float* a = (const float*) _a;
const float* b = (const float*) _b;
if(*a > *b) return 1; // first item is bigger than the second one -> return 1
else
if(*a == *b) return 0; // equality -> return 0
else return -1; // second item is bigger than the first one -> return -1
}
// example for the use of qsort()
void QSortExample()
{
float field[100];
::randomize(); // initialize random-number-generator
for(int c=0;c<100;c++) // randomize all elements of the field
field[c]=random(99);
// sort using qsort()
qsort((void*) field, /*number of items*/ 100, /*size of an item*/ sizeof(field[0]),
/*comparison-function*/ CmpFunc);
// display first ten elements of the sorted field
printf("The first ten elements of the sorted field are .../n");
for(int c=0;c<10;c++)
printf("element #%d contains %.0f/n", c+1, field[c]);
printf("/n");
}
4. 如何对静态C++成员函数实现callback
这和对C函数实现callback一样。静态成员函数不需要一个对象来调用因此与带有相同调用约定的C函数具有相同的签名,调用参数和返回类型。
5. 如何对非静态C++成员函数执行callback
(1)包装方法
指向非静态成员函数的指针不同于通常的C函数指针,因为它们需要被传递给类对象的this指针。因此通常的函数指针和非静态成员函数具有不同和不兼容的签名!如果想callback一个具体类的成员,只需要改变代码从一个通常的函数指针为指向成员函数的指针。但如果想callback一个任意类的非静态成员函数呢?这个有点困难。我们需要写一个静态成员函数作为包装器。一个静态成员函数与C函数有着相同的签名!然后转换想要调用的成员函数所在对象的指针类型为void*,然后把它作为一个额外参数或者通过全局变量传递给包装器。如果使用的是全局变量,很重要的是确信它会总指向正确的对象!当然,也可以为成员函数传递调用参数。包装器将void指针转换为指向对应类实例的指针,然后调用成员函数。
(2)Example A: 将类实例的指针作为额外参数传递
函数DoltA处理类TClassA的对象。因此类TClassA的指针和静态包装函数TClassA::Wrapper_To_Call_Display的指针被传递给DoltA。这个包装器就是callback函数。我们可以写任意其他像TClassA一样的类,然后用DoltA使用他们,只要这些其他的类提供必要的函数。注意:如果想自己设计callback接口时,这个方案很有用,它会优于另外一种方案。
//-----------------------------------------------------------------------------------------
// 3.5 Example A: Callback to member function using an additional argument
// Task: The function 'DoItA' makes something which implies a callback to
// the member function 'Display'. Therefore the wrapper-function
// 'Wrapper_To_Call_Display is used.
#include <iostream.h> // due to: cout
class TClassA
{
public:
void Display(const char* text) { cout << text << endl; };
static void Wrapper_To_Call_Display(void* pt2Object, char* text);
/* more of TClassA */
};
// static wrapper-function to be able to callback the member function Display()
void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string)
{
// explicitly cast to a pointer to TClassA
TClassA* mySelf = (TClassA*) pt2Object;
// call member
mySelf->Display(string);
}
// function does something which implies a callback
// note: of course this function can also be a member function
void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text))
{
/* do something */
pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)"); // make callback
}
// execute example code
void Callback_Using_Argument()
{
// 1. instantiate object of TClassA
TClassA objA;
// 2. call 'DoItA' for <objA>
DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
}
(3)Example B: 类实例的指针存储在全局变量中
函数DoltB处理类TClassB的对象。静态函数TClassB::Wrapper_To_Call_Display的指针被传递给了DoltB。这个包装器是一个callback函数。包装器使用全局变量void* pt2Object,然后显式的将它转换为TClassB的实例。重要的是总是要初始全局变量指向正确的类实例。你可以写任意的像TClassB一样的类,然后用DoltB使用它们,只要这些其它类提供必要的函数。注意:如果有一个现存的callback接口并且不能改变,使用这种方案较好。但是这并非是一个好的方案应为全局变量很危险且可能导致严重错误。
//-----------------------------------------------------------------------------------------
// 3.5 Example B: Callback to member function using a global variable
// Task: The function 'DoItB' makes something which implies a callback to
// the member function 'Display'. Therefore the wrapper-function
// 'Wrapper_To_Call_Display is used.
#include <iostream.h> // due to: cout
void* pt2Object; // global variable which points to an arbitrary object
class TClassB
{
public:
void Display(const char* text) { cout << text << endl; };
static void Wrapper_To_Call_Display(char* text);
/* more of TClassB */
};
// static wrapper-function to be able to callback the member function Display()
void TClassB::Wrapper_To_Call_Display(char* string)
{
// explicitly cast global variable <pt2Object> to a pointer to TClassB
// warning: <pt2Object> MUST point to an appropriate object!
TClassB* mySelf = (TClassB*) pt2Object;
// call member
mySelf->Display(string);
}
// function does something which implies a callback
// note: of course this function can also be a member function
void DoItB(void (*pt2Function)(char* text))
{
/* do something */
pt2Function("hi, i'm calling back using a global ;-)"); // make callback
}
// execute example code
void Callback_Using_Global()
{
// 1. instantiate object of TClassB
TClassB objB;
// 2. assign global variable which is used in the static wrapper function
// important: never forget to do this!!
pt2Object = (void*) &objB;
// 3. call 'DoItB' for <objB>
DoItB(TClassB::Wrapper_To_Call_Display);
}