C语言中的回调

C语言中的回调

refer to: http://blog.youkuaiyun.com/maochengtao/article/details/49928431

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

什么是回调

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

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

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

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

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

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

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

过程语言中的回调(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,
GetCallBackfCallback;

 

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


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; // 出错

 

指针pcallee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针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 
#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;
}

 

2.4 面向对象语言中的回调(Delphi

DephiC++一样,为了保持与过程语言Pascal的兼容性,它在引入面向对象机制的同时,保留了以前的结构化特性。因此,对回调的实现,也有两种截然不同的模式,一种是结构化的函数回调模式,一种是面向对象的接口模式。

2.4.1 回调函数

回调函数类型定义:

type
   TCalcFunc=function (a:integer;b:integer):integer;

 

按照回调函数的格式自定义函数的实现,如

function Add(a:integer;b:integer):integer
begin
  result:=a+b;
end;
function Sub(a:integer;b:integer):integer
begin
  result:=a-b;
end;

 

回调的使用

function Calc(calc:TcalcFunc;a:integer;b:integer):integer

 

下面,我们就可以在我们的程序里按照需要调用这两个函数了

c:=calc(add,a,b);//c=a+b
c:=calc(sub,a,b);//c=a-b

 

2.4.2 回调对象

什么叫回调对象呢,它具体用在哪些场合?首先,让我们把它与回调函数对比一下,回调函数是一个定义了函数的原型,函数体则交由第三方来实现的一种动态应用模式。要实现一个回调函数,我们必须明确知道几点:该函数需要那些参数,返回什么类型的值。同样,一个回调对象也是一个定义了对象接口,但是没有具体实现的抽象类(即接口)。要实现一个回调对象,我们必须知道:它需要实现哪些方法,每个方法中有哪些参数,该方法需要放回什么值。

因此,在回调对象这种应用模式中,我们会用到接口。接口可以理解成一个定义好了但是没有实现的类,它只能通过继承的方式被别的类实现。Delphi中的接口和COM接口类似,所有的接口都继承与IInterface(等同于IUnknow),并且要实现三个基本的方法QueryInterface, _AddRef, _Release

· 定义一个接口 

type IShape=interface(IInterface)
 procedure Draw;
end

· 

· 实现回调类 

type TRect=class(TObject,IShape)
 protected
      function QueryInterface(const IID: TGUID; out Obj): HResult;

stdcall;
      function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
    public
   procedure Draw;
end;
type TRound=class(TObject,IShape)
 protected
      function QueryInterface(const IID: TGUID; out Obj): HResult;

stdcall;
      function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
    public
   procedure Draw;
end;

· 

· 使用回调对象 

procedure MyDraw(shape:IShape);
var 
shape:IShape;
begin
shape.Draw; 
end;

· 

如果传入的对象为TRect,那么画矩形;如果为TRound,那么就为圆形。用户也可以按照自己的意图来实现IShape接口,画出自己的图形:

MyDraw(Trect.Create);
MyDraw(Tround.Create);

 

2.4.3 回调方法

回调方法(Callback Method)可以看作是回调对象的一部分,Delphiwindows消息的封装就采用了回调方法这个概念。在有些场合,我们不需要按照给定的要求实现整个对象,而只要实现其中的一个方法就可以了,这是我们就会用到回调方法。

回调方法的定义如下:

TNotifyEvent = procedure(Sender: TObject) of object; 
TMyEvent=procedure(Sender:Tobject;EventId:Integer) of object;

 

TNotifyEvent Delphi中最常用的回调方法,窗体、控件的很多事件,如单击事件、关闭事件等都是采用了TnotifyEvent。回调方法的变量一般通过事件属性的方式来定义,如TCustomForm的创建事件的定义:

property OnCreate: TNotifyEvent read FOnCreate write FOnCreate

stored IsForm;

 

我们通过给事件属性变量赋值就可以定制事件处理器。

用户定义对象(包含回调方法的对象):

type TCallback=Class
    procedure ClickFunc(sender:TObject);
end;
procedure Tcallback.ClickFunc(sender:TObject);
begin
  showmessage('the caller is clicked!');
end;

 

窗体对象:

type TCustomFrm=class(TForm)
  public
 procedure RegisterClickFunc(cb:procedure(sender:Tobject) of

object);
end;
procedure TcustomFrm..RegisterClickFunc(cb:TNotifyEvent);
begin
  self.&111nClick=cb;
end;

 

使用方法:

var
  frm:TcustomFrm;
begin
  frm:=TcustomFrm.Create(Application);
  frm.RegisterClickFunc(Tcallback.Create().ClickFunc);
end;

 

回调在分布式计算中的应用(CORBA

3.1 回调接口模型

CORBA的消息传递机制有很多种,比如回调接口、事件服务和通知服务等。回调接口的原理很简单,CORBA客户和服务器都具有双重角色,即充当服务器也是客户客户。

回调接口的反向调用与正向调用往往是同时进行的,如果服务端多次调用该回调接口,那么这个回调接口就变成异步接口了。因此,回调接口在CORBA中常常充当事件注册的用途,客户端调用该注册函数时,客户函数就是回调函数,在此后的调用中,由于不需要客户端的主动参与,该函数就是实现了一种异步机制。

CORBA规范我们知道,一个CORBA接口在服务端和客户端有不同的表现形式,在客户端一般使用桩(Stub)文件,服务端则用到框架(Skeleton)文件,接口的规格采用IDL来定义。而回调函数的引入,使得服务端和客户端都需要实现一定的桩和框架。下面是回调接口的实现模型:



3.1.1 范例
 

下面给出了一个使用回调的接口文件,服务端需要实现Server接口的框架,客户端需要实现CallBack的框架:

module cb
{
 interface CallBack;
 interface Server;
interface CallBack 
{
     void OnEvent(in long Source,in long msg);
};
   interface Server 
{
     long RegisterCB(in CallBack cb);
  void UnRegisterCB(in long hCb);
};
};

 

客户端首先通过同步方式调用服务端的接口RegistCB,用来注册回调接口CallBack。服务端收到该请求以后,就会保留该接口引用,如果发生某种事件需要向客户端通知的时候就通过该引用调用客户方的OnEvent函数,以便对方及时处理。

http://www.eefocus.com/iamzhang/blog/12-02/238706_2bd3e.html

 

<think>首先,用户的问题是:“我想理解C语言回调函数的使用和实现方式 请问C语言 回调函数 实现方式 使用方法”。我需要根据系统级指令和用户可见层的要求来构建回答。 系统级指令: 1. 所有行内数学表达式使用$...$格式。 2. 独立公式使用$$...$$格式并单独成段。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识。 用户可见层: 1. 回答结构清晰,帮助用户逐步解决问题。 2. 尽量保证回答真实可靠。 3. 参考站内引用:引用[1]到[5],这些是关于C语言回调函数的引用。 参考引用内容: - [^1]:介绍回调函数的基本概念和原理。 - [^2]:解释回调函数通过函数指针实现。 - [^3]:回调函数是作为参数传递给其他代码的可执行代码。 - [^4]:编写和使用回调函数的流程。 - [^5]:一个不定参数回调函数的例子。 用户的问题集中在C语言回调函数的使用和实现方式。我需要基于这些引用构建一个结构清晰的回答。 回答结构: 1. **介绍回调函数**:简要解释什么是回调函数,基于引用。 2. **实现方式**:详细说明如何使用函数指针来实现回调函数。 3. **使用方法**:提供代码示例和步骤。 4. **总结**:概括关键点。 5. **相关问题**:生成相关问题。 由于系统指令要求使用中文回答,并且生成相关问题,我需要确保回答是中文的。 关键点从引用中提取: - 回调函数允许函数在执行过程中调用另一个函数([^1])。 - 通过函数指针实现([^2])。 - 回调函数作为参数传递给其他函数([^3])。 - 使用流程:自定义函数指针,定义回调函数,注册回调函数([^4])。 - 代码示例:不定参数回调函数([^5])。 我需要创建一个代码示例来展示回调函数实现和使用。引用[5]提供了一个不定参数的例子,但用户可能更关心一般用法。我可以基于引用[4]的流程创建一个简单的回调函数示例。 步骤: 1. 定义函数指针类型。 2. 定义回调函数。 3. 定义一个函数(如库函数)接受函数指针作为参数。 4. 在适当的时候调用回调函数。 代码示例使用C语言。 最后,生成相关问题。 确保LaTeX:用户问题中没有数学表达式,所以可能不需要使用$...$或$$...$$,但如果涉及数学,我会使用。回调函数不涉及数学,所以可能不需要。 回答结构: - **标题**:C语言回调函数实现与使用 - **部分**: - 1. 什么是回调函数? - 2. 如何实现回调函数? - 3. 如何使用回调函数?(包括代码示例) - 4. 实际应用 - 5. 总结 - 相关问题 在回答中自然地添加引用标识。例如,当提到某个概念时,添加[^1]等。 完整回答:</think>### C语言回调函数实现与使用 回调函数是C语言编程中的一种核心技术,它允许函数在执行过程中调用另一个函数,从而实现事件处理、异步编程等功能。通过函数指针实现回调函数,可以增强代码的灵活性和可重用性。下面我将逐步解释回调函数的概念、实现方式和使用方法,并提供代码示例。回答基于可靠的编程实践和参考引用[^1][^2][^3][^4][^5]。 #### 1. **什么是回调函数?** 回调函数是一种作为参数传递给其他函数的可执行代码。当特定事件发生时(如数据就绪或定时器触发),接收函数会“回调”这个参数函数。例如: - 在事件驱动编程中,回调函数处理用户输入或系统事件[^1]。 - 在异步操作中,回调函数在任务完成后执行后续逻辑[^3]。 核心原理是:通过函数指针将函数引用传递给另一个函数,实现解耦和动态行为[^2]。 #### 2. **回调函数实现方式** 回调函数通过函数指针实现。函数指针是一个指向函数内存地址的变量,允许在运行时动态调用函数。实现步骤包括: 1. **定义函数指针类型**:指定回调函数的签名(返回类型和参数列表)。 2. **编写回调函数**:实现一个符合指针类型签名的函数。 3. **注册回调函数**:将回调函数的指针传递给接收函数(如库函数)。 4. **触发回调**:接收函数在特定条件下调用回调函数[^4]。 例如,定义一个函数指针类型: ```c typedef void (*CallbackFunc)(int); // 定义函数指针类型,接受int参数,无返回值 ``` 这里,`CallbackFunc` 是一个指向函数的指针,该函数接受一个 `int` 参数并返回 `void`。 #### 3. **回调函数的使用方法** 以下是一个完整的C语言示例,展示回调函数实现和使用流程。代码包括: - 定义回调函数类型。 - 实现一个接收回调函数的函数(模拟事件触发)。 - 在主函数中注册和测试回调。 ```c #include <stdio.h> // 步骤1: 定义函数指针类型 typedef void (*CallbackFunc)(int); // 步骤2: 编写回调函数实现 void myCallback(int data) { printf("回调函数被调用,数据: %d\n", data); // 事件处理逻辑 } // 步骤3: 定义接收回调函数的函数(模拟事件处理器) void eventHandler(CallbackFunc callback) { int eventData = 42; // 模拟事件数据 printf("事件发生,触发回调...\n"); callback(eventData); // 调用回调函数 } int main() { // 步骤4: 注册回调函数并触发 eventHandler(myCallback); // 将myCallback的指针传递给eventHandler return 0; } ``` **代码解释:** - `myCallback` 是用户定义的回调函数,处理传入的数据。 - `eventHandler` 接受一个函数指针参数,并在内部调用它(模拟事件触发)。 - 在 `main` 中,将 `myCallback` 注册到 `eventHandler`,触发回调。 - **输出:** ``` 事件发生,触发回调... 回调函数被调用,数据: 42 ``` **关键使用方法:** - **注册流程**:应用层定义回调函数 → 调用注册函数传递函数指针 → 硬件或库函数在事件发生时回调[^4]。 - **参数传递**:回调函数可以接受任意参数,如引用[^5]所示的不定参数示例(使用 `va_list` 处理可变参数)。 - **错误处理**:在回调中添加逻辑检查,例如返回错误码。 - **实际应用**:在GUI库中处理按钮点击、在网络编程中处理数据接收[^1][^3]。 #### 4. **回调函数的实际应用场景** - **事件驱动系统**:如嵌入式开发中处理传感器数据[^4]。 - **设计模式**:实现观察者模式或策略模式,提升代码模块化[^2]。 - **异步编程**:在文件I/O或多线程中,回调函数在操作完成后执行[^3]。 优势:解耦调用方和被调用方,支持动态行为;缺点:过度使用可能导致“回调地狱”,需谨慎设计。 #### 5. **总结** 回调函数在C语言中通过函数指针实现,核心步骤包括定义指针类型、实现回调函数、注册和触发。它广泛应用于事件处理、异步编程和库设计,能显著提升代码灵活性。使用时需注意指针安全和参数匹配[^2][^4]。掌握这一技术后,您可以扩展至更复杂的场景,如多线程回调或结合C++的面向对象回调
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值