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 内核在自身私有作用域中自动创建事件对象、完成参数传递,用户仅需通过回调形参接收事件数据即可。

相关推荐
Linux-lucky18 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
禁默18 小时前
启元 Q1 二次开发实践:Ubuntu 部署与仿真验证,从 PrimeConsole 到 ROS 2 Humble + RViz
linux·运维·ubuntu
skywalk816318 小时前
把 去也网站 从 Ubuntu 搬到 FreeBSD:一次 Service Jail + nginx 的部署实录
linux·运维·nginx·ubuntu·freebsd
爱莉希雅&&&20 小时前
GitLab 完整学习笔记
linux·运维·服务器
风笙97821 小时前
[文件系统]文件系统挂载与卸载详解
linux
GeW21 小时前
新质生产力路线图解读:数据库性能优化与Linux调优专场
linux·数据库
陈希1231 天前
Linux 服务器性能排查手册(2026):CPU、内存、磁盘IO、网络四大瓶颈定位
linux·服务器·网络
测试界的小码农1 天前
【Linux 命令实战(03):文件创建、复制、移动与删除,掌握 touch、mkdir、cp、mv、rm】
linux·服务器·php
没差c1 天前
winsw将jar、nginx等注册为Windows服务
windows·nginx·jar
byte轻骑兵1 天前
实战 1:修改 BlueZ 源码,添加自定义开机日志
linux·aiot·bluez·电脑蓝牙·嵌入式蓝牙