lv_binding_js 代码解读

概览

此项目主函数入口在src\engine\engine.cpp中,主流程大概如下:

  1. 初始化 txiki.js 运行时(TJS_Initialize)
  2. 创建 JS 虚拟机实例(JS 上下文 + libuv 事件循环)
  3. 向 JS 全局对象注入 lvgljs 命名空间
  4. 初始化 LVGL 硬件抽象层(HAL)和窗口
  5. 启动 30ms 定时器,循环调用 lv_timer_handler()
  6. 进入 txiki.js 事件循环,执行用户 JS 代码

详细流程

整个注册流程大概如下:

cpp 复制代码
NativeRenderInit(ctx, ns)	render_bootstrap.cpp	总入口,注册所有原生绑定
NativeComponentInit(ctx, ns)	component.cpp	注册所有组件
NativeComponentXxxInit(ctx, ns)	*_wrap.cpp	每个组件各自的注册函数
WRAPPED_JS_* 系列宏	component.hpp	通过宏自动生成通用 JS 绑定函数
registerComponent(config)	react/components/config.ts	JS 端注册 React 组件

以 View 为例:注册全流程如下:

第 1 层:C++ 总入口

cpp 复制代码
engine.cpp
  └─ NativeRenderInit(ctx, ns)          // render_bootstrap.cpp
       └─ NativeComponentInit(ctx, obj)  // component.cpp
            └─ NativeComponentViewInit(ctx, component_obj)  // view_wrap.cpp
注册完成后,就可以在JS端利用以下路径进行访问:
globalThis['lvgljs'].NativeRender.NativeComponents.View

第 2 层:View 的 wrap 文件(view_wrap.cpp)

① 这里定义了一些里宏,用来生成所有组件通用方法

cpp 复制代码
WRAPPED_JS_SETSTYLE(View, "View")           // → NativeCompSetStyle
WRAPPED_JS_AddEventListener(View, "View")   // → NativeCompAddEventListener
WRAPPED_APPEND_CHILD(View, "View")          // → NativeCompAppendChild
WRAPPED_REMOVE_CHILD(View, "View")          // → NativeCompRemoveChild
WRAPPED_INSERT_CHILD(View, "View")          // → NativeCompInsertChildBefore
WRAPPED_JS_Align(View, "View")              // → NativeCompSetAlign
STYLE_INFO(View, "View")                    // → style_funcs (width/height/left/top)
WRAPPED_JS_CLOSE_COMPONENT(View, "View")    // → NativeCompCloseComponent

每个宏会自动展开为一个 C 函数,C端调用时,通过 JS_GetOpaque 取出 C++ 实例指针,然后调用对应方法。

② 定义 JS 方法列表

cpp 复制代码
static const JSCFunctionListEntry ComponentProtoFuncs[] = {
    TJS_CFUNC_DEF("nativeSetStyle",    0, NativeCompSetStyle),
    TJS_CFUNC_DEF("addEventListener",  0, NativeCompAddEventListener),
    TJS_CFUNC_DEF("appendChild",      0, NativeCompAppendChild),
    TJS_CFUNC_DEF("removeChild",      0, NativeCompRemoveChild),
    TJS_CFUNC_DEF("align",            0, NativeCompSetAlign),
    TJS_CFUNC_DEF("close",            0, NativeCompCloseComponent),
    JS_OBJECT_DEF("style", style_funcs, countof(style_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE),
};

③ 定义构造函数

cpp 复制代码
static JSValue ViewConstructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) {
    // 1. 从 JS 参数中取出 uid
    jsUid = JS_GetPropertyStr(ctx, arg, "uid");
    uid = JS_ToCString(ctx, jsUid);
    // 2. 创建 JS 对象,绑定到 ViewClassID
    obj = JS_NewObjectProtoClass(ctx, proto, ViewClassID);
    // 3. 创建 C++ View 实例
    s = (COMP_REF*)js_mallocz(ctx, sizeof(*s));
    s->uid = uid;
    s->comp = new View(uid, NULL);   // ← 关键:创建 C++ 组件
    // 4. 将 C++ 实例指针绑定到 JS 对象
    JS_SetOpaque(obj, s);
    return obj;
}

④ 定义析构函数

cpp 复制代码
static void ViewFinalizer(JSRuntime *rt, JSValue val) {
    COMP_REF *th = (COMP_REF *)JS_GetOpaque(val, ViewClassID);
    delete static_cast<View*>(th->comp);  // 释放 C++ 实例
    js_free_rt(rt, th);
}

⑤ 注册函数(核心)

cpp 复制代码
void NativeComponentViewInit(JSContext* ctx, JSValue ns) {
    // 1. 分配唯一的 ClassID
    JS_NewClassID(JS_GetRuntime(ctx), &ViewClassID);
    // 2. 注册 JS 类(含 finalizer)
    JS_NewClass(JS_GetRuntime(ctx), ViewClassID, &ViewClass);
    // 3. 创建 prototype 并绑定方法列表
    JSValue proto = JS_NewObject(ctx);
    JS_SetPropertyFunctionList(ctx, proto, ComponentProtoFuncs, countof(ComponentProtoFuncs));
    JS_SetClassProto(ctx, ViewClassID, proto);
    // 4. 创建构造函数并关联 prototype
    JSValue obj = JS_NewCFunction2(ctx, ViewConstructor, "View", 1, JS_CFUNC_constructor, 0);
    JS_SetConstructor(ctx, obj, proto);
    // 5. 将 "View" 挂到命名空间上
    JS_DefinePropertyValueStr(ctx, ns, "View", obj, JS_PROP_C_W_E);
}

第 3 层:JS 端注册(React 侧)

bash 复制代码
// src/render/react/components/view/index.ts

// 1. 获取原生类
const bridge = globalThis[Symbol.for('lvgljs')];
const NativeView = bridge.NativeRender.NativeComponents.View;

// 2. JS 端继承原生类
export class ViewComp extends NativeView {
    // 封装 React 兼容的方法
}

// 3. 注册 React 组件
export const View = registerComponent<ViewProps, ViewComp>(new ViewConfig());
当在JS调用时,整体数据流如下:

JS: new NativeView({ uid: "1" })
  → ViewConstructor()
    → new View(uid, NULL)        // C++ 对象创建
    → JS_SetOpaque(obj, ref)     // 绑定到 JS 对象

JS: view.appendChild(child)
  → NativeCompAppendChild()      // 注册的 C 函数
    → JS_GetOpaque(obj) → ref
    → static_cast<View*>(ref->comp)->appendChild(child)

JS 对象被 GC 回收
  → ViewFinalizer()
    → delete View*               // C++ 对象释放

总结

如果要新增一个组件应该怎么办呢?

创建 xxx_wrap.cpp,模仿 view_wrap.cpp 的模式

定义宏展开 + 方法列表 + Constructor + Finalizer + Init 函数

在 component.cpp 的 NativeComponentInit() 中添加调用

在 JS 端创建 XxxComp extends NativeXxx 并调用 registerComponent()

相关推荐
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
伞伞悦读3 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
C语言小火车3 天前
C/C++ 为什么需要编译器?
开发语言·c++
李少兄3 天前
JavaScript 隐式全局变量解析
javascript
霍霍的袁3 天前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio
孙启超3 天前
【AI开发之Rust】第 11 课:智能指针与内部可变性
开发语言·后端·rust
此生决int3 天前
深入理解C++系列(20)——C++11(下)
开发语言·c++
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端