C语言中的回调函数(Callback Function)

1 定义和使用场合

回调函数是指 使用者自己定义一个函数,实现这个函数的程序内容,然后把这个函数(入口地址)作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。

这一设计允许了底层代码调用在高层定义的子程序(如图1-1所示)。C语言中回调函数主要通过函数指针的方式实现。

 

图1-1 回调函数在软件系统的调用结果

 

回调的用途十分广泛:[1]

例如,假设有一个函数,其功能为读取配置文件并由文件内容设置对应的选项。若这些选项由散列值(hash function)所标记,则让这个函数接受一个回调会使得程序设计更加灵活:函数的调用者可以使用所希望的散列算法,该算法由一个将选项名转变为散列值的回调函数实现;因此,回调允许函数调用者在运行时调整原始函数的行为

回调的另一种用途在于处理信号量。例如一个POSIX程序可能在收到SIGTERM信号时不愿立即终止;为了保证一切运行良好,该程序可以将清理函数注册为SIGTERM信号对应的回调。

回调亦可以用于控制一个函数是否作为:Xlib允许自定义的谓词(NSPredicate)用于决定程序是否希望处理特定的事件。

复制代码
#include <iostream>
#include <string>
using namespace std;

typedef void (*FP)(char* s);    //结构体表示函数指针
void f1(char* s){cout<<s;}
void f2(char* s){cout<<s;}
void f3(char* s){cout<<s;}

int main(int argc,char* argv[])
{
    int funcselector=0;           //定义一个整数用于控制待执行的函数
    void* a[]={f1,f2,f3};   //定义了指针数组,这里a是一个普通指针
    a[0]("Hello World!\n"); //编译错误,指针数组不能用下标的方式来调用函数

    FP f[]={f1,f2,f3};      //定义一个函数指针的数组,这里的f是一个函数指针
    
    /* Handle of funselector */       //此处用于处理funselector,控制待执行的函数
    f[funselector]("Hello World!\n"); //正确,函数指针的数组进行下标操作可以进行函数的间接调用
    
    return 0;
}     
复制代码

上面一个例子中提现了回调函数的部分作用。这里f1,f2,f3表示三个功能不相同的函数(举例说明:f1实现最大值输出,f2实现平均值输出,f3实现最小值输出)。总结一下回调函数的一些优势:

采用funcselector作为标志量,选择待执行的函数很方便的控制了函数的流程和工序。

f1,f2,f3三个特定函数模块化明显,便于设计者去维护、修改。如图1-1所示,很多系统中software library会完全封装,这样开发者只能通过回调函数去修改函数功能。

分析函数思路更加清晰,在lwip中大量使用回调函数,开发者可以根据回调函数的调用流程分析系统结构。

 

2 结构解析

回调函数主要结构有三部分组成:主函数、调用函数和被调函数(如图1-1所示)。C语言中,被调函数通常以函数指针(指向对应函数的入口地址)的形式出现。 

这里给出一个最简单的回调函数结构,并解析相关数据结构。

复制代码
//定义回调函数
void PrintfText() 
{
    printf("Hello World!\n");
}

//定义实现回调函数的"调用函数"
void CallPrintfText(void (*callfuct)())
{
    callfuct();
}

//实现函数回调
int main(int argc,char* argv[])
{
    CallPrintfText(PrintfText);
    return 0;
}
复制代码

调用函数向其函数中传递 void (*callfuct)(void) 这是一个 void callfuct(void) 函数的入口地址,即PC指针可以通过移动到该地址执行void callfuct(void) 函数,可以通过类比数组来理解。

实现函数调用中,函数调用了“调用函数”,再在其中进一步调用被“调用函数”。相比于主函数直接调用“被调函数”,这种方法为使用者,而不是开发者提供了灵活的接口。另外,函数入口可以像变量一样设定同样为开发者提供了灵活性。

 

3 实例分析

 这里分析一个lwip中较为复杂的回调函数使用范例:

复制代码
void httpd_init(void)
{
    struct tcp_pcb * pcb;
    pcb = tcp_new();
    tcp_bind(pcb,IP_ADDR_ANY,80);
    pcb = tcp_listen(pcb);
    tcp_accept(pcb, http_accept);        
}

void
tcp_accept(struct tcp_pcb * pcb, err_t(* accept)(void *arg, struct tcp_pcb *newpcb, err_t err))

static err_t http_accept(void *arg, struct tcp_pcb * pcb, err_t err)
{
    /* set the prio of callback function, important */
    tcp_setprio(pcb, TCP_PRIO_MIN);
    tcp_recv(pcb, http_recv);
    return ERR_OK;    
}    

void 
tcp_recv(struct tcp_pcb * pcb, err_t (* recv)(void * arg, struct tcp_pcb * tpcb, struct pbuf * p, err_t err))

static err_t http_recv(void *arg, struct tcp_pcb * pcb, struct pbuf *p, err_t err)
{
    /* html handler by user's definition */
    /* use tcp_write(pcb, message, sizeof message, 0) to send message */
}
复制代码

这里调用两个回调函数,模块化分离了tcp和http,感兴趣可以看看lwip的RAW部分。

 

4 参考资料

[1] https://zh.wikipedia.org/wiki/%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0

[2] http://partow.net/programming/templatecallback/index.html

[3] http://www.partow.net/programming/hashfunctions/index.html

[4] http://blog.csdn.net/callmeback/article/details/4242260

[5] http://www.cnblogs.com/chenyuming507950417/archive/2012/01/02/2310114.html


转载自:https://www.cnblogs.com/jontian/p/5619641.html

展开阅读全文

关于C语言中回调函数的问题!!

10-15

纯C,VS2005下运行如下代码:(其中函数musicopentimer也为静态的)rnstatic void MediaNotify(void * pUser, AEEMediaCmdNotify * pCmdNotify)rnrn CApp * pMe = (CApp *)pUser;rn if (pCmdNotify->nCmd != MM_CMD_PLAY)rn return;rnrn switch(pCmdNotify->nStatus) rn case MM_STATUS_START:rn break;rn case MM_STATUS_ABORT:rn case MM_STATUS_DONE:rn if(IMEDIA_Play(pMe->m_pMedia) != SUCCESS)rn rn ISHELL_SetTimer(pMe->a.m_pIShell,0,(PFNNOTIFY)musicopentimer,pMe);rn rn elsern rn ISHELL_CancelTimer(pMe->a.m_pIShell,(PFNNOTIFY)musicopentimer,pMe);rn rn rnrn以上代码运行时一切正常,但我在程序中加入如下代码,运行至该函数尾时会报如下错误,rn[color=#FF0000]Run-Time Check Failure #2 - Stack around the variable 'nVolume' was corrupted.[/color]rnstatic void MediaNotify(void * pUser, AEEMediaCmdNotify * pCmdNotify)rnrn CApp * pMe = (CApp *)pUser;rn if (pCmdNotify->nCmd != MM_CMD_PLAY)rn return;rnrn switch(pCmdNotify->nStatus) rn case MM_STATUS_START:rn break;rn case MM_STATUS_ABORT:rn case MM_STATUS_DONE:rn if(IMEDIA_Play(pMe->m_pMedia) != SUCCESS)rn rn ISHELL_SetTimer(pMe->a.m_pIShell,0,(PFNNOTIFY)musicopentimer,pMe);rn rn elsern rn ISHELL_CancelTimer(pMe->a.m_pIShell,(PFNNOTIFY)musicopentimer,pMe);rn rn rn rn uint16 nVolume;rn int nError;rn nError = IMEDIA_GetVolume(pMe->m_pMedia, &nVolume);rn DBGPRINTF_FATAL("nError = %d, nVolume = %d", nError, nVolume);rn rnrn根据错误提示,我在变量nVolume和nError之前分别加上了static,这样运行的时候就没有问题了。rn上面两个变量加上static之后该变量是在程序运行前分配空间,并不是函数中的临时变量。rnrnrn请问以上两个变量不能使用局部非静态的临时变量是因为该函数是静态函数的原因还是回调函数的原因呢?为啥不能直接使用? 论坛

没有更多推荐了,返回首页