Chinaunix首页 | 论坛 | 博客
  • 博客访问: 199219
  • 博文数量: 48
  • 博客积分: 1430
  • 博客等级: 上尉
  • 技术积分: 640
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-29 13:24
文章分类

全部博文(48)

文章存档

2014年(2)

2013年(1)

2011年(1)

2009年(8)

2008年(36)

我的朋友

分类: C/C++

2008-10-31 17:56:13

级别: 初级

陈家朋, 系统架构师和技术顾问, 杭州迈可行通信技术有限公司

2003 年 3 月 01 日


1.什么是回调?

软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。



对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。

对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。

Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。

对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。

下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。






回调在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);

如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。







到目前为止,我们只讨论了函数指针及回调而没有去注意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,尽管两者有相同的返回值和参数列

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
#include

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;
}

一段英文说明:

Why Should You Use Callback Functions?

Because they uncouple the caller from the callee. The caller doesn't care who
the callee is; all it knows is that there is a callee with a certain prototype
and probably some restriction (for instance, the returned value can be int, but
certain values have certain meanings).

If you are wondering how is that useful in practice, imagine that you want to
write a library that provides implementation for sorting algorithms (yes, that
is pretty classic), such as bubble sort, shell short, shake sort, quick sort,
and others. The catch is that you don't want to embed the sorting logic (which
of two elements goes first in an array) into your functions, making your library
more general to use. You want the client to be responsible to that kind of
logic. Or, you want it to be used for various data types (ints, floats, strings,
and so on). So, how do you do it? You use function pointers and make callbacks.

A callback can be used for notifications. For instance, you need to set a timer
in your application. Each time the timer expires, your application must be
notified. But, the implementer of the time'rs mechanism doesn't know anything
about your application. It only wants a pointer to a function with a given
prototype, and in using that pointer it makes a callback, notifying your
application about the event that has occurred. Indeed, the SetTimer() WinAPI
uses a callback function to notify that the timer has expired (and, in case
there is no callback function provided, it posts a message to the application's
queue).

Another example from WinAPI functions that use callback mechanism is
EnumWindow(), which enumerates all the top-level windows on the screen.
EnumWindow() iterates over the top-level windows, calling an
application-provided function for each window, passing the handler of the
window. If the callee returns a value, the iteration continues; otherwise, it
stops. EnumWindows() just doesn't care where the callee is and what it does with
the handler it passes over. It is only interested in the return value, because
based on that it continues its execution or not.

However, callback functions are inherited from C. Thus, in C++, they should be
only used for interfacing C code and existing callback interfaces. Except for
these situations, you should use virtual methods or functors, not callback
functions.

阅读(1007) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~