『7x24小时有问必答』
回调能解耦,也能把代码绕晕。嵌入式项目里要先说清谁注册、谁调用、回调在哪个上下文执行、能不能阻塞、生命周期由谁负责。把这些边界写清楚,回调才能从“到处跳”变成稳定扩展点。

---

一、回调混乱:谁调用谁说不清

最危险的写法是只存一个裸函数指针:
static  void  (*rxCallback)(uint8_t  data);

void  uartIrqHandler(void)

{

       // 中断里直接调业务回调,风险很高

      rxCallback(uartReadByte());

}

它的问题不是函数指针本身,而是没有上下文、没有生命周期、没有执行约束。

---

二、回调接口要带上下文

typedef  void  (*EventCallback_t)(void  *userData,  uint32_t  event);

typedef  struct  {

      EventCallback_t callback;

       void  *userData;

       bool  active;

} CallbackSlot_t;

注册函数要检查参数,并把上下文一起保存:
bool  callbackRegister(CallbackSlot_t *slot,

                                 EventCallback_t callback,

                                  void  *userData)

{

       if  ((slot ==  NULL) || (callback ==  NULL)) {

             return  false;

      }

      slot->callback = callback;

      slot->userData = userData;

      slot->active =  true;

       return  true;

}

---

三、调用边界:ISR里尽量只投递事件

嵌入式架构核心原则:中断上下文极简执行,禁止耗时、阻塞、复杂业务逻辑。所有回调业务统一剥离到主循环或任务线程执行
void  uartIrqHandler(void)

{

       uint8_t  data = uartReadByte();

       // ISR 中只投递事件,不直接跑复杂业务回调

      eventPost(EVENT_UART_RX, data);

}

在主线程/任务中统一调度、触发回调,保证系统实时性与稳定性:
void  callbackInvoke(CallbackSlot_t *slot,  uint32_t  event)

{

       if  ((slot !=  NULL) && slot->active) {

            slot->callback(slot->userData, event);

      }

}

这套隔离机制,彻底避免了中断阻塞、优先级翻转、系统卡死等核心问题。

---

四、注册和调用边界

问题
要写清楚
谁注册
初始化阶段还是运行时
谁调用
ISR、任务、线程还是主循环
能否阻塞
ISR 回调必须短
生命周期
注销前对象是否还活着
错误处理
回调失败如何反馈

---

五、千万不要这样做

回调里再次注册回调:  生命周期会变复杂,容易出现悬空指针。
中断里跑业务回调:  一旦回调阻塞,实时性就没了。
没有注销机制:  模块卸载或重初始化后,旧函数指针还在。
错误被吞掉:回调失败至少要有计数或日志,不要静默丢失。

---

六、总结

回调架构的关键不是函数指针语法,而是调用边界。谁注册、谁调用、在哪个上下文、能不能阻塞,这几件事写清楚,回调才会变成扩展点,而不是隐藏调用链。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码