回调函数设计方法

引入:

你显示器不亮了,你不知道怎么弄,那你就问在外地干IT的大表哥,你大表哥告诉你修理的方法,然后需要你自己来操作。

你大表哥知道怎么弄,但是自己不去弄,而是由你去弄。

换句话说,你大表哥实现了修理你显示器的方法,但他不会自己去调用,而是由你去调用。那么你大表哥告诉你的修机器的方法就是回调函数。

在这个比喻里,你自己 作为主调方,有实际的需求——修显示器,但是没有方法,求教表哥的时候,表哥给你的方法 就是一个 函数地址,当你按照大表哥的方法执行的时候,就是 执行了一个回调函数了。

在工程设计中,尤其在底层库设计的时候,很多时候,库的开发者并不能预测今后使

用这段代码的程序员需要这个函数做具体什么工作,这时候,就需要使用回调设计。

C 和 C++都提供这类回调支持,C 的建议是使用函数指针,回调实现,C++则通过对基类的继承,对基类中虚函数的再设计来实现。不过,根据笔者经验,在这点上,C 的方式比C++方式要轻灵,并且更加灵活。因此,在笔者的工程开发中,一般使用回调函数设计,不太使用虚函数机制。

回调函数其实就是函数指针的应用,在 C 中,一切数据均可以指针化表示,函数本身

其实也可以,当我们以正确的构型调用一个函数指针时,其效果和直接调用函数本身,完全一样。

另外,由于现代操作系统的 C 亲密性,很多操作系统级的 api 设计都可以看到回调函数 ,比如我们常见的线程函数,甚至进程本身,其实都是操作系统的回调函数,beginthread 这类启动线程的调用,一般就是把指定的线程函数指针,在系统的线程表中,注册一个新的表项,系统下一轮时间循环,自动会根据这个表项,回调该指针,进而实现应用程序线程对时间片的获取。并且,这个过程,一般都是纯 C 的,和 C++无关。

从某种意义上说,现代并行计算,是建立在 C 的回调模型上的。作为程序员,对于回调函数,应该有很深入的认识,并能熟练应用。

回调函数的设计非常简单,不过,这里面首先要搞清楚两个身份,一个是回调函数的

设计者,一个是使用者,但二者都是程序员。

在后文中,使用 回调模型设计者和使用者来区分这两个身份。

回调模型设计者:

作为回调模型的设计者,首先需要定义一个回调函数构型,因为 C 语言就算再灵活,

也需要知道函数原型是怎样的,才能确保使用者是正确调用,避免崩溃。

  1. typedef void (*_APP_INFO_OUT_CALLBACK)(char* szInfo,void* pCallParam);

1、typedef,这是我们显式定义一种新的变量类型,这个变量类型,就是这一个回调函

数指针的类型。以后使用这个指针的设计者和使用者,都可以使用

_APP_INFO_OUT_CALLBACK 这个变量类型来定义自己的指针变量。

2、本回调函数使用 void 作为返回值,是因为这个特殊应用。其实很多时候,有个约定 ,一般回调函数使用 bool 作为返回值,这在某些循环遍历的场合,当使用者感到自己的数据已经找到,循环无需继续,可以返回个 false,设计者就知道,可以不再循环了。这体现出使用者不是完全被动的接受回调,也可以通过返回值影响回调发起方的逻辑。

3、char* szInfo 这是业务数据,这里不再细说。

4、void* pCallParam,这个非常关键,所有回调函数的设计者,一定要帮助使用者传递

一根 void*的指针,并透传到每一次回调调用中。

例子:

创建一个支持回调的 类

  1. class CStultzLowDebug
  2. {
  3. public:
  4. CStultzLowDebug(char* szPathName,
  5. char* szAppName,
  6. //构造函数传入回调函数和参数,可以是 null
  7. _APP_INFO_OUT_CALLBACK pInfoOutCallback=null,
  8. void* pInfoOutCallbackParam=null);
  9. //保存在对象内部,方便 Debug 等功能函数调用
  10. _APP_INFO_OUT_CALLBACK m_pInfoOutCallback;
  11. void* m_pInfoOutCallbackParam;
  12. };

构造函数 的具体实现:

  1. CStultzLowDebug::CStultzLowDebug(char* szPathName,
  2. char* szAppName,
  3. _APP_INFO_OUT_CALLBACK pInfoOutCallback,
  4. void* pInfoOutCallbackParam)
  5. {
  6. m_pInfoOutCallback=pInfoOutCallback; //回调函数指针保存
  7. m_pInfoOutCallbackParam=pInfoOutCallbackParam; //参数指针保存
  8. //…
  9. }

设计者 对回调函数 的调用方式

  1. int CStultzLowDebug::Debug2File(char *szFormat, ...)
  2. {
  3. //…
  4. if(m_pInfoOutCallback) //标准写法,先判断指针有效性
  5. {
  6. m_pInfoOutCallback(szInfoOut, //像函数一样调用
  7. m_pInfoOutCallbackParam); //这里在帮助透传指针
  8. }
  9. //…
  10. }

总结 回调函数的设计的特点:

1、先定义回调函数原型,顺便定义一个新的指针变量类型。

2、设计者以该回调函数指针变量类型定义新的变量,实现参数传递和数据保存。

3、调用前先检查指针有效性,避免跳到空指针处,造成崩溃。

回调模型使用者

作为使用者来说,如果回调函数设计者均基于上述方法设计,其调用程序设计也可以

形成简单规律和套路。

使用者首先必须以回调函数构型构建一个函数,这就是将来的回调函数实体,设计者

的模块会跳至此处运行。使用者在这个函数内部,直接使用传来的变量 szInfo 即可,这就是每次 Debug 模块输出的字符串。

void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);

但有一点注意,如果是 C 里面,可以这样直接声明和实现函数即可。但在 C++的类中 ,不能这样直接写。这是由于 C++的编译器,为每一个类成员函数,提供了一个默认的隐含指针 this作为参数,指向本次实例化的对象,其类型就是这个类本身。因此,如下所述,这个函数就不对了。

  1. class CStultzLowDebug
  2. {
  3. private:
  4. void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);
  5. };

此时的回调函数原型,由于是类成员函数,有隐含指针,因此相当于如下原型

  1. void ApplicationInfomationOutCallback(
  2. CStultzLowDebug* this, //这是 C++编译器在编译时强行添加的
  3. char* szInfo,
  4. void* pCallParam);

这时,我们再和回调函数原型比较,发现多了一个 this 指针。

两个函数不是一个构型,函数指针类型不匹配,调用将会失败。

因此,所有的回调函数,一旦写在类里面,必须用 static 修饰为静态成员函数。

  1. class CStultzLowDebug
  2. {
  3. private:
  4. //请注意这里的 static 修饰
  5. static void ApplicationInfomationOutCallback(
  6. char* szInfo,void* pCallParam);
  7. };

C++规定,对于静态类成员函数,将不提供隐含的 this 指针,因此,函数的编译后本体和书写时的声明完全一样,这样就可以把这个函数作为回调函数。

但这随之带来另外一个问题,就是没有了 this 指针,使用起来很不方便。我们知道 ,

C++的面向对象设计中,其对象的核心定义就是“一批数据和针对该数据的所有方法的集

合。”,这是面向对象程序设计的精髓。

因此,一个类的成员函数方法,一般说来,都和这个类实例化的对象所包含的数据密

切相关,程序中需要不断访问本对象的成员变量或其他成员函数,也就需要频繁访问本对象指针 this。

因此 在C++里,我们可以有如下的解决方案,即 传参

回调函数设计者有义务为使用者透传一根  void* 的参数指针

因此 在 实际使用时,将this指针 作为 参数,传给 回调函数,那么 就可以直接使用this

,从而操作 类中的数据了

如果 有 多个参数 需要 传递,就使用 结构体 指针的方式。

参考文献:《0 bug C/C++商用工程之道》

时间: 2024-10-10 13:18:06

回调函数设计方法的相关文章

实例介绍 Java(android) 回调函数使用方法

在Android开发中经常用到回调机制,其中最典型的就是控件被触发的实现方式,简单而言,如Button被Click后,是系统调用了OnClick方法,而我们为Button注册了OnClickListener监听器,当被触发Click后,OnClickListener中的OnClick方法就会被回调,我们就能在其中执行相应操作了. 下面举一个简单的例子介绍回调的实现方式: 回调函数使用的简单例子 程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序.程序员B要让a调用自己的程序

回调函数使用方法二

class CRegAuto : public QObject { Q_OBJECT public: static CRegAuto *getInstance(); static void ClearInstance(); typedef void (CRegAuto::*Callback)(int value); QHash<int, Callback> cheatCommands; void Initialize(); void processCallBack(); void add(in

回调函数以及钩子函数的概念

钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统.每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权.这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递.对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权.要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来安装这个钩子函数,这个函数的原型是

javascript回调函数(模式)原理和示例深入分析

   广大网友读懂了我之前论述的javascript原理这篇文章很容易懂 回调函数来自一种著名的编程范式--函数式编程,在基本层面上,函数式编程指定的了函数的参数.函数式编程虽然现在的使用范围变小了,但它一直被"专业的聪明的"程序员看作是一种难懂的技术,以前是这样,未来也将是如此. 幸运的是,函数式编程已经被阐述的像你我这样的一般人也能理解和使用.函数式编程最主要的技术之一就是回调函数,你很快会阅读到,实现回调函数就像传递一般的参数变量一样简单.这项技术如此的简单,以至于我都怀疑为什么

DLL与EXE之间的通讯调用 以及 回调函数的线程执行空间

dll 与 exe 之间的通讯方式有很多种, 本文采用回调函数的方法实现, 本文也将研究多线程,多模块的情况下,回调函数所在的线程, 啥也不说了,先附上代码: 下面的是dll模块的的, dll的工程文件: [delphi] view plaincopy library DllAPP; uses windows, SysUtils, Classes, DllClass in 'DllClass.pas'; {$R *.res} var GDllServer: TDllServer; functio

JavaScript中回调函数的使用

在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A.我们就说函数A叫做回调函数.如果没有名称(函数表达式),就叫做匿名回调函数. 在实际应用中,可以这么应用,一个方法进行获取数据源,另一个方法(回调函数)可以通过数据源在页面上进行展示,可以根据具体的需求进行展示就行,如果多个地方用到这个数据源,可以写不同的回调函数,将此函数传入这个方法中即可. 来,咱们通过实例可以一目了然 获取公司信息的小例子 1.获取公司信息数据源的方法(

使用注册回调函数

我们的日常开发中会发现有时候使用回调函数能方便的提高程序兼容性和扩展性,那么具体回调函数怎么使用呢,下面做个笔记,有不正确的地方,欢迎指出. 一.C语言中的使用方法 首先使用回调函数,就会用到怎么注册回调函数这个问题.你需要告诉底层代码,它需要调用的函数是什么.那么就得事先定义好回调函数的类型. 如: typedef void (*eventHandler_f) (int param); 然后定义实现你的注册函数.注册函数的作用就是告诉底层代码,使用者想让你调用的函数是那个 #define MA

理解和使用 JavaScript 中的回调函数

原文:http://javascriptissexy.com/ 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以"存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值". 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后

c# 如何调用非托管函数3-实现回调函数

部分dll函数是需要回调的,因此我们在托管代码中调用的时候,必须先创建回调函数,然后将该函数的指针作为参数传递给dll函数. 以下以EnumWindows函数为例,演示了使用回调函数的方法: 一 找到函数签名 EnumWindows具有以下签名: BOOL EnumWindows(WNDENUMPROC lpEnumFunc,LPARAM lParam); 其中 lpEnumFunc 就是应用程序定义的回调函数的指针:BOOL CALLBACK EnumWindowsProc(HWND hwnd