看流星社区

 找回密码
 注册账号
查看: 1602|回复: 0

回调函数(02)

[复制链接]

该用户从未签到

发表于 2013-5-15 09:22:46 | 显示全部楼层 |阅读模式
回调函数是由用户撰写,而由操作系统调用的一类函数,回调函数可以把调用者和被调用者分开,调用者(例如操作系统)不需要关心被调用者到底是哪个函数,它所知道的就是有这么一类函数,这类满足相同的函数签名(函数原型,参数,返回值等),由用户书写完毕后在被调用就可以了。实现上回调函数一般都是通过函数指针来实现的。
      典型的回调函数是MFC 下的定时器处理函数ontimer,你只需要添加这个消息响应函数,然后在初始化的时候将ontimer指针传递给操作系统,操作系统就会按照设定好的时间来循环调用ontimer函数了。
      你甚至可以将main函数理解成回调函数,因为它不会被客户程序员调用,只会被客户程序员撰写,然后由操作系统来调用。
      回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可以在回调函数里完成你要做的事。
      回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在windows编程使用的场合很多,比如Hook回调函数:MouseProc,GetMsgProc以及EnumWindows,DrawState的回调函数等等,还有很多系统级的回调过程。
      使用回调函数的编程模式,可以根据不同的需求传递不同的回调函数地址,或者定义各种回调函数的原形(同时也需要改变使用回调函数的参数和返回值约定),实现多种回调事件处理,可以使程序的控制灵活多变,也是一种高效率的,清晰的程序模块之间的耦合方式。在一些异步或复杂的程序系统里尤其有用 -- 你可以在一个模块(如DLL)里专心实现模块核心的业务流程和技术功能,外围的扩展的功能只给出一个回调函数的接口,通过调用其他模块传递过来的回调函数地址的方式,将后续处理无缝地交给另一个模块,随它按自定义的方式处理。
      需要注意一点, 回调函数一般都要声明为全局的. 如果要在类里使用回调函数, 前面需要加上 static  , 其实也相当于全局的.

      声明函数指针
      回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:

      void f();// 函数原型

      上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:

      void (*) ();

    让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

      // 获得函数指针的大小
      unsigned psize = sizeof (void (*) ());

      // 为函数指针声明类型定义
      typedef void (*pfv) ();

      pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。

      指针变量应该有一个变量名:

      void (*p) (); //p是指向某函数的指针

      p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:

      void func()
      {
      /* do something */
      }
      p = func;

      p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。

      传递回调函数的地址给调用者

      现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:

      void caller(void(*ptr)())
      {
      ptr(); /* 调用ptr指向的函数 */
      }
      void func();
      int main()
      {
      p = func;
      caller(p); /* 传递函数地址到调用者 */
      }

      如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
      调用规范
      到目前为止,我们只讨论了函数指针及回调而没有去注意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,尽管两者有相同的返回值和参数列。
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-4-29 14:38

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表