1. 什么是回调 
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:
同步调用、回调和异步调用。

同步调用, 是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;
回调,     是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;
异步调用, 是一种类似消息或事件的机制,不过它的调用方向刚好相反,
          接口的服务在收到某种讯息或发生某种事件时,会主动通知客户端(即调用客户端的接口)。

回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。
同步调用是三者当中最简单的,而回调又常常是异步调用的基础,
因此,下面我们着重讨论回调机制在不同软件架构中的实现。 
 
对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),
客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,
让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。

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

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

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

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

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

2 过程语言中的回调(C) 
2.1 函数指针 
回调在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);

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

2.2 参数传递规则 
到目前为止,我们只讨论了函数指针及回调而没有去注意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; // 出错, 前者是__cdecl ,后者是__stdcall

指针p和callee()的类型不兼容,因为它们有不同的调用规范。
因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

2.3 应用举例 
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 <stdio.h>
#include <stdlib.h>

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

3 面向对象语言中的回调: Callback在C/C++中的实现
Callback是这样的一类对象(在这里不能简单的理解为"回调函数"了):
你注册一个函数,以及调用它时的参数,希望在满足某个条件时,以这些注册的函数调用这个回调,完成指定的操作.
很多地方会使用到这个概念.
比如,UI程序中,注册一个函数,当某个鼠标事件发生的时候自动调用;
比如,创建一个线程,线程开始运行时,执行注册的函数操作.

Callback的出现,本质上是因为很多操作都有异步化的需要---
你不知道它什么时候会执行,只需要告诉它,在执行的时候,调用我告诉你的操作即可.
尽管使用的地方不尽相同,但是从程序的角度上看,做的事情都是差不多的.

要实现一个Callback,最大的难点在于,变化的参数和需要统一的对外接口之间的矛盾.
也就是说,回调函数执行时参数的数量是你无法预知的.而你需要对外提供一个统一的接口,
调用该接口的不需要关注到注册进去的到底是什么,有几个参数,具体的执行留到回调真正执行的时候再去处理.

简单介绍一下目前我所知道的几种方法,有C++的, 
1) 使用模板 
将不同参数的类型,作为模板的参数.比如:

#include <stdio.h>

class Closure 

public: 
    virtual ~Closure(){} 
    virtual void Run() {}

protected: 
    Closure(){} 
};

template<class T> class Callback0 : public Closure 

public: 
    typedef void (T::*Done)(); 
  
public:  
    Callback0(T *obj, Done run) : object_(obj) , run_(run) 
    { 
    }

virtual void Run() 
    { 
        (object_->*run_)(); 
     } 
private: 
    T *  object_; 
    Done run_; 
};

template<class T, class T1> class Callback1 : public Closure 

public: 
    typedef void (T::*Done)(T1); 
  
public:  
    Callback1(T *obj, Done run, T1 arg) : object_(obj) 
    , run_(run) 
    , arg0_(arg) 
    { 
    }

virtual void Run() 
    { 
        (object_->*run_)(arg0_); 
    } 
private: 
    T *object_; 
    Done run_; 
    T1 arg0_; 
};

class Test 

public: 
  void Run0() 
  { 
    printf("in Test::Run0/n"); 
  }

void Run1(int i) 
  { 
    printf("in Test::Run1/n"); 
  } 
};

template<class T> 
Closure* 
NewCallback(T *obj, void (T::*member)()) 

  return new Callback0<T>(obj, member); 
}

template<class T, class T1> 
Closure* 
NewCallback(T *obj, void (T::*member)(T1), T1 P) 

  return new Callback1<T, T1>(obj, member, P); 
}

int main() 

  Test test;

Closure *callback0 = NewCallback(&test, &Test::Run0); 
  callback0->Run(); 
  delete callback0;

Closure *callback1 = NewCallback(&test, &Test::Run1, 1); 
  callback1->Run(); 
  delete callback1;

return 0; 
}

在这里,定义了一个虚拟基类Closure,它对外暴露一个接口Run,
也就是,使用它的时候只需要使用Closure指针->Run即可以执行注册的操作.
需要注意的是,Closure的构造函数声明为protected,也就是仅可以被子类调用. 
接下来,定义的Closure'子类都是模板类,其中的模板都是参数,
我分别实现了两种子类,分别是不带参数的和带一个参数的.将回调函数需要的参数,保存在具体的子类对象中. 
最后,对外构造一个Closure指针时,最好也提供一致的接口,这里分别为两种子类实现了NewCallback函数. 
剩下的,理解起来应该不难.

这种实现方法,看明白的就知道,其实难点不多.
它将回调函数和传递给回调函数的参数放在了一个类中,当外部调用Run接口的时候,再根据内部的实现来具体进行操作. 
但是,我本人很不喜欢模板满天飞的代码,所以应该还有些别的方法来实现吧?

2) 不使用模板,将参数和回调分离,分别对参数和回调进行抽象 
CEGUI是一款开源的游戏UI项目,早几年我还在做着3D引擎程序员梦的时候,曾经看过一些,对它的一些代码还有些印象. 
里面对UI事件的处理,也使用了类似Callback的机制(这种使用场景最开始的时候曾经说过,所以应该不会感到意外). 
在CEGUI中,一个事件由一个虚拟基类Event定义,处理事件的时候调用的是它的纯虚函数fireEvent,
而这个函数的参数之一是EventArgs--这又是一个虚拟基类.

所以,熟悉面向对象的人,应该可以很快的反应过来了:
在Event的子类中实现fireEvent,而不同的函数参数,可以从EventArgs虚拟基类中派生出来. 
于是,具体回调的时候,仅仅需要调用 Event类指针->fireEvent(EventArgs类指针)就可以了. 
(我在这里对CEGUI的讲解,省略了很多细节,仅仅关注到最关注的点,感兴趣的可以自己去看看代码)

对比1)和2)两种解决方法,显然对我这样不喜欢模板的人来说,更喜欢2).
除了模板的代码读起来比较头大,以及模板会让代码量增大之外.
喜欢2)的原因还在于,C对"类模板"机制的支持实在是欠缺,至今除了使用宏之外,
似乎找不到很好的办法能够实现类C++的模板机制.
但是,如果采用2)的继承接口的方式,C就可以很清楚的实现出来.所以就有了下面C的实现:

3) C的实现. 
有了2)的准备,使用C来实现一个类似的功能,应该很容易了,下面贴代码,应该很清楚的:

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h>

typedef struct event 

  void (*fireEvent)(void *arg); 
  void *arg; 
}event_t;

typedef struct event_arg1 

  int value; 
}event_arg1_t;

void fireEvent_arg1(void *arg) 

  event_arg1_t *arg1 = (event_arg1_t*)arg;

printf("arg 1 = %d/n", arg1->value); 
}

#define NewEvent(event, eventtype, callback)      / 
  do {                                            / 
      *(event) = (event_t*)malloc(sizeof(event_t)); / 
      assert(*(event));                              / 
      (*(event))->arg = (eventtype*)malloc(sizeof(char) * sizeof(eventtype)); / 
      assert((*(event))->arg);                         / 
      (*(event))->fireEvent = callback;             / 
  } while (0)

#define DestroyEvent(event)                       / 
  do {                                            / 
    free((*(event))->arg);                        / 
    free(*(event));                               / 
  } while(0)

int main() 

  event_t *event;

NewEvent(&event, event_arg1_t, fireEvent_arg1); 
  ((event_arg1_t*)(event->arg))->value = 100; 
  event->fireEvent(event->arg);

DestroyEvent(&event);

return 0; 
}

回调函数原理及应用实例相关推荐

  1. php回调函数原理和实例

    php回调函数原理和实例 原理 自己调用自己 称之为"递归",而不是回调 你也知道回调的关键是这个回 既然是回,那么就有一个谁是主体的问题,因为回调是往回调用的意思 我调用了函数A ...

  2. 【转】JS回调函数--简单易懂有实例

    JS回调函数--简单易懂有实例 初学js的时候,被回调函数搞得很晕,现在回过头来总结一下什么是回调函数. 我们先来看看回调的英文定义:A callback is a function that is ...

  3. PHP - 回调函数概念与用法实例分析 - 学习/实践

    1.应用场景 主要用于理解回调函数的概念, 对比JavaScript中的回调函数, 更加深刻理解回调函数的本质, 以及如何高效使用~~~ 2.学习/操作 1. 文档阅读 https://www.jb5 ...

  4. python的用途实例-Python基础之函数原理与应用实例详解

    本文实例讲述了Python基础之函数原理与应用.分享给大家供大家参考,具体如下: 目标 函数的快速体验 函数的基本使用 函数的参数 函数的返回值 函数的嵌套调用 在模块中定义函数 01. 函数的快速体 ...

  5. 回调函数原理与Python实现

    回调函数的定义与普通函数并没有本质的区别,但一般不直接调用,而是作为参数传递给另一个函数,当另一个函数中触发了某个事件.满足了某个条件时就会自动调用回调函数.下面的代码用来删除可能会包含只读属性文件的 ...

  6. mysql 自定义函数实例_mysql自定义函数原理与用法实例分析

    本文实例讲述了mysql自定义函数原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是函数 函数的创建 函数的调用 函数的查看 函数的修改 函数的删除 首发日期:2018-04-18 什么 ...

  7. Block、委托、回调函数原理剖析(在Object C语境)——这样讲还不懂,根本不可能!...

    开篇:要想理解Block和委托,最快的方法是搞明白"回调函数"这个概念. 做为初级选手,我们把Block.委托.回调函数,视为同一原理的三种不同名称.也就是说,现在,我们把这三个名 ...

  8. JS回调函数——简单易懂有实例

      初学js的时候,被回调函数搞得很晕,现在回过头来总结一下什么是回调函数.什么是JS?(点击查看) 下面先看看标准的解释: <script language="javascript& ...

  9. Java回调函数理解和应用

    #Java回调函数理解和应用 所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法,这样子说你是不是有点晕晕的. 在未理解之前,我也是一脸懵逼,等我理 ...

  10. C语言回调函数demo(帮助理解)以海康isapiExternDemo回调函数为例

    参考文章:c语言指针回调函数最全demo实例(简单明了一看就会) 回更 20220303 \demo\base\isapiExternDemo\isapiExternDemo.c 这边海康定义了一个回 ...

最新文章

  1. linux 任务计划 权限设置,Linux系统 文件权限+计划任务+日志系统
  2. 120.数据缓存cache的基本概念
  3. 大系统观:第2章 系统论概述
  4. WideCharToMultiByte和MultiByteToWideChar函数的用法(转)
  5. 宽带651以及光猫红色灯闪烁-故障维修
  6. deepin系统中.txt文件图标显示内容问题_deepin从兴致勃勃到彻底放弃
  7. Linux下部署Hadoop伪分布模式
  8. python中debug有什么用途_Python debug 总结
  9. sass编译css(转自阮一峰)
  10. VSCode调试Python时终端输出中文乱码解决方法2
  11. 在同时使用animation和translate时,translate无效
  12. npm 下载为什么很慢?解决方案来了
  13. MAC下学习UNIX网络编程
  14. python存钱挑战_案例(4):52周存钱法
  15. 数据分析必备43个Excel函数
  16. c# Monitor
  17. 访问知乎出现【出了一点问题,我们正在解决,去往首页】解决方案
  18. c# API串口通信
  19. 安卓统一推送联盟成立!不卡顿的安卓系统终于来了!
  20. 设计表:多张表存储学生成绩及各种信息

热门文章

  1. Mac不用Boot Camp 安装双系统
  2. 台式计算机m4350,联想 商用台式机 ThinkCentre M4350t
  3. sqlite3_英英词典
  4. 福昕阅读器的 注册码
  5. Xshell 和 Xftp 免费下载
  6. java 正则表达式匹配_Java 正则表达式匹配
  7. 人脸关键点数据集整理
  8. 【教程】NEC e-Border Client的设置图文教程(中文版)
  9. 如何在 Ubuntu 上转换图像、音频和视频格式
  10. 怎么反编译java dll_JAVA 反编译工具,如何在Java中调用DLL方法