LVGL 普通函数指针与结构体函数指针原理

前言

在 LVGL 事件开发中,开发者经常使用 lv_obj_add_event_cb 绑定回调函数。多数初学者无法理解:回调函数如何被控件保存、事件参数从何而来、两种函数指针的区别是什么。本文从零、零基础、标准化讲解整套原理。

一、普通函数指针

1. 定义

不依赖任何结构体、独立存在的函数指针,称为普通函数指针。

LVGL 官方对事件回调统一类型定义:

复制代码
typedef void (*lv_event_cb_t)(lv_event_t * e);

该类型仅规定函数格式,不属于任何结构体,属于标准普通函数指针类型。

2. 实例

用户自定义的回调函数,其地址即为普通函数指针:

void test_cb(lv_event_t * e) { }

3. 特点

  • 独立存在,不归属任何控件或结构体

  • 仅提供函数执行地址,等待被调用

  • 开发者传入 lv_obj_add_event_cb 的回调函数,均为普通函数指针

二、结构体函数指针

1. 定义

将函数指针作为结构体成员进行保存,该指针称为结构体函数指针。

普通函数指针是"裸函数地址",结构体函数指针是"被结构体收纳保存的函数地址"。

2. LVGL 底层真实结构(简化)

LVGL 并不会直接在 lv_obj_t 中存放回调,而是通过独立事件结构体管理:

cs 复制代码
// 事件描述结构体:保存单个回调信息 
typedef struct lv_event_dsc_t 
{ 
    lv_event_cb_t cb; 
    // 结构体函数指针(真正保存回调的位置) 
    uint32_t event_type; 
    // 事件类型 
    void * user_data; 
    // 用户数据 
} 

lv_event_dsc_t; 

// 控件结构体 
typedef struct lv_obj_t 
{ 
    // 控件坐标、样式等基础属性 
    lv_event_dsc_t * event_list; 
    // 控件挂载的所有事件 
} 

lv_obj_t;

3. 特点

  • 必须依托结构体存在,不能独立使用

  • 每一个控件拥有独立的事件结构体,控件之间回调互不干扰

  • 控件能够绑定多个事件,依靠事件结构体链表实现

三、lv_obj_add_event_cb 工作原理

1. 函数作用

lv_obj_add_event_cb 的核心功能:将用户自定义的普通函数指针,赋值绑定到目标控件所属事件结构体的函数指针成员中,实现回调函数与控件事件的关联。

2. 执行流程

复制代码
`lv_obj_add_event_cb(btn, test_cb, LV_EVENT_CLICKED, NULL);`
  1. test_cb:用户提供的普通函数指针

  2. LVGL 为 btn 控件创建/取出对应的 lv_event_dsc_t 事件结构体

  3. 将普通函数指针 test_cb 赋值给事件结构体内部的 cb 成员,完成回调绑定

  4. 最终回调被保存在控件内部,等待事件触发

四、回调参数 lv_event_t *e 的来源(核心难点)

1. 参数来源说明

开发者从未定义、从未创建 lv_event_t 变量,但回调可以正常接收参数。

原因:事件对象由 LVGL 内核自动生成,不属于用户代码。

2. 底层触发逻辑

当按钮被点击时,LVGL 内核会自动执行事件分发逻辑。为方便读者直观理解完整执行流程,下文为教学简化伪代码,仅用于原理演示,并非工程可编译代码,也不会与 LVGL 内核源码冲突。所有变量均为演示用途,真实逻辑由 LVGL 内核私有代码实现。

bash 复制代码
// 教学伪代码:仅用于原理解释,用户代码不能使用、无需定义 
// dsc 为模拟内核临时指针,仅演示逻辑,非用户可用变量 
        
    lv_event_dsc_t *dsc = btn->event_list;             

// 1. 模拟内核:自动创建局部事件对象 

    lv_event_t evt; 

// 2. 模拟内核:自动填充本次点击的所有事件信息 
// 3. 模拟内核:通过结构体函数指针调用用户回调     

    dsc->cb(&evt);

3. 参数传递关系

  • 内核局部变量 evt:系统自动生成

  • &evt:系统自动传递给回调函数

  • 用户回调形参 lv_event_t *e:接收系统传入的地址

4. 变量名无关性

参数传递仅传递内存地址,与变量名称无关:

  • 内核内部事件变量名称由官方源码固定定义,不可修改;

  • 仅用户侧回调形参名称可自定义,修改为 e、ev、event 均不影响程序运行;

五、最终核心总结

1. 普通函数指针:用户编写的回调函数,独立存在,无归属。

2. 结构体函数指针:存放在控件事件结构体内部的指针,用于保存回调。

3. 绑定原理:API 将用户的普通函数指针,保存进控件的结构体函数指针中。

4. 参数原理:事件触发时,LVGL 内核在自身私有作用域中自动创建事件对象、完成参数传递,用户仅需通过回调形参接收事件数据即可。

相关推荐
可涵不会debug4 天前
LangChain 进阶之路:LangGraph 图工作流、有状态智能体完整入门教程
microsoft
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
rockmelodies4 天前
# Windows Server2008 R2 Standard(SP1镜像U盘)重置本地管理员密码【完整详细步骤】
windows·密码破解
未济4 天前
linux 配置环境变量
linux
傲世仙尊4 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
_艾伦 耶格尔.4 天前
进程间通信
linux
Liuqy-054 天前
Linux IO编程——静态库、动态库
linux
彧azz4 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
-梅4 天前
linux(8) 软硬链接
linux·运维·服务器
AIgorithmGEEK5 天前
[Linux]线程三部曲(上):一个执行流的诞生——从操作系统一路拆到 pthread_create
linux·线程·pid