news 2026/4/18 11:38:07

CallBackRef函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CallBackRef函数

CallBackRef 是什么:为什么回调里既要函数指针,也要一个 void* 上下文

在 Xilinx 驱动(例如XSpiPs)里,经常能看到这样的接口:

voidXSpiPs_SetStatusHandler(XSpiPs*InstancePtr,void*CallBackRef,XSpiPs_StatusHandler FunctionPtr);

很多人第一次看会困惑:既然已经传了回调函数FunctionPtr,为什么还需要一个CallBackRef

答案是:FunctionPtr决定“做什么”(代码),而CallBackRef决定“对谁做”(上下文数据)。
这两者解决的是两个不同维度的问题。


1. FunctionPtr:回调函数指针(代码地址)

回调函数指针告诉驱动:发生事件时调用哪段逻辑。例如 SPI 传输完成、模式错误等事件:

  • 传输完成 → 通知上层继续下一步
  • 错误发生 → 记录日志、重试或上报

驱动不可能替所有应用场景写死策略,所以用函数指针把“策略”交给用户。


2. CallBackRef:回调上下文指针(数据地址)

C 语言的函数指针不携带上下文,回调函数执行时,如果要访问某个对象或状态,就需要一个入口指针。CallBackRef就是这个入口。

它的特点:

  • 类型是void *:可以指向任何用户自定义的数据结构
  • 驱动不会解析它:仅在触发回调时原样传回
  • 在回调函数中由用户将其强制转换回真实类型

这相当于在 C 语言里用“函数指针 + void*”手工模拟面向对象里的“成员函数回调”或“闭包”。


3. 为什么不能只用一个回调函数?

如果只用FunctionPtr,回调函数怎么知道它在服务哪个对象?

例如一个工程里可能有多个 SPI 设备或多条总线:

  • SPI0 控制 AD9528
  • SPI1 控制 Flash 或 ADC
  • 或同一 SPI 上挂多个片选设备

如果只有函数指针,回调函数就必须依赖全局变量来区分“这是哪一个设备的完成事件”,工程规模一大就会:

  • 难以复用
  • 容易写错
  • 多实例难维护

有了CallBackRef,同一个回调函数可以用于多实例,只要传不同的上下文指针即可。


4. 最典型的用法:传一个“上下文结构体”

这是最推荐的方式:用结构体保存你在回调里需要用到的所有信息。

typedefstruct{volatileintdone;u32 last_event;u32 bytes;}spi_ctx_t;staticspi_ctx_tctx;staticvoidspi_status_cb(void*ref,u32 event,u32 bytes){spi_ctx_t*c=(spi_ctx_t*)ref;// CallBackRef -> 真实类型c->last_event=event;c->bytes=bytes;c->done=1;// 通知上层“传输完成/出错”}

注册回调:

XSpiPs_SetStatusHandler(&SpiInstance,&ctx,spi_status_cb);

这样,驱动内部发生事件时会调用:

InstancePtr->StatusHandler(InstancePtr->StatusRef,EventCode,Bytes);

其中StatusRef就是你注册的CallBackRef


5. 在 FreeRTOS 中更常见:把“句柄”作为 CallBackRef

在 RTOS 场景里,回调通常发生在 ISR 中。最常见做法是 ISR 回调里唤醒任务,典型手段包括:

  • SemaphoreHandle_t信号量
  • TaskHandle_t任务通知
  • QueueHandle_t队列事件

这些 handle 在 FreeRTOS 实现中往往就是“内核对象控制块的引用/指针”,正好可放进void*,因此可以直接作为CallBackRef传入。

例如用信号量等待 SPI 完成:

staticSemaphoreHandle_t spi_sem;staticvoidspi_status_cb(void*ref,u32 event,u32 bytes){SemaphoreHandle_t sem=(SemaphoreHandle_t)ref;BaseType_t hpw=pdFALSE;if(event==XST_SPI_TRANSFER_DONE){xSemaphoreGiveFromISR(sem,&hpw);portYIELD_FROM_ISR(hpw);}}voidinit_spi_cb(void){spi_sem=xSemaphoreCreateBinary();XSpiPs_SetStatusHandler(&SpiInstance,(void*)spi_sem,spi_status_cb);}

核心思想:
CallBackRef 指向“该通知谁”的目标,回调函数负责“怎么通知”。


6. CallBackRef 使用注意事项(工程必读)

6.1 生命周期必须足够长

回调是异步发生的(中断触发),所以不能把栈上局部变量地址作为 CallBackRef

voidbad(){intlocal;XSpiPs_SetStatusHandler(&SpiInstance,&local,cb);// 错:local 很快失效}

推荐使用:

  • 全局/static变量
  • 长生命周期对象(设备结构体、RTOS 句柄)
  • 动态分配(需要你自己管理释放时机)

6.2 回调里要做的事情尽量短

尤其在 ISR 回调中:

  • 只做置位、发信号量/通知、推队列等
  • 不做耗时打印、不做阻塞等待、不做长循环

6.3 类型转换要一致

驱动不会检查类型,你传什么,回调就按你传的类型去转换。类型不一致会导致严重错误。


7. 一句话总结

  • FunctionPtr(回调函数)=行为/策略(代码)
  • CallBackRef=上下文/对象引用(数据)

二者结合,让驱动在不依赖全局变量的前提下支持:

  • 多实例
  • 可复用回调
  • ISR/RTOS 同步
  • 应用层自定义处理策略
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 21:00:20

【Dify私有化部署终极指南】:从零开始掌握企业级部署全流程

第一章:Dify私有化部署概述Dify 是一个开源的低代码 AI 应用开发平台,支持快速构建基于大语言模型的智能应用。私有化部署允许企业将 Dify 完整运行在自有服务器或私有云环境中,保障数据安全与系统可控性,适用于对合规性、隐私保护…

作者头像 李华
网站建设 2026/4/17 1:13:49

聚势华商·智创未来 | 华商北京校友会年会庆典圆满落幕

2026年1月3日,星河华商书院华商北京校友会年会庆典在北京西国贸大酒店1号宴会厅隆重举行。本次活动以“聚势华商智创未来”为核心口号,汇聚“聚势凝心凝共识,智创赋能启新程,华商同心传薪火,逐梦同行向未来”的奋进力量…

作者头像 李华
网站建设 2026/4/17 22:29:29

GLM-4.6V-Flash-WEB模型推理速度实测报告

GLM-4.6V-Flash-WEB模型推理速度实测报告 在当前AI应用加速落地的背景下,多模态大模型正从实验室走向真实业务场景。但一个现实问题始终存在:很多视觉语言模型虽然能力强大,却“跑得太慢”——一次图文问答动辄耗时半秒以上,用户还…

作者头像 李华
网站建设 2026/4/16 19:08:16

GLM-4.6V-Flash-WEB在电子签名验证中的安全性考量

GLM-4.6V-Flash-WEB在电子签名验证中的安全性考量 在电子合同被广泛用于贷款审批、远程签约和政务办理的今天,一个看似合法的PDF文件可能暗藏玄机:签名区域被人用PS替换过,或者同一份合同里出现了三处一模一样的“手写签名”。这类视觉层面的…

作者头像 李华