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()

相关推荐
不知名的老吴1 小时前
线程的生命周期之线程“插队“
java·开发语言·python
kaikaile19952 小时前
数字全息图处理系统(C# 实现)
开发语言·c#
Patrick_Wilson2 小时前
router.replace 之后紧跟 reload,页面为什么无限刷新?
javascript·react.js·浏览器
秋93 小时前
Go语言(Golang)开发工程师全景解析:岗位职责·语言优势与使用场景·各城市薪资·发展前景·高考志愿填报(2026版)
开发语言·golang·高考
mONESY4 小时前
JavaScript 栈、队列、数组与链表核心知识点总结
javascript·面试
huangdong_4 小时前
1688商品图片采集技术解析:登录态处理与SKU图自动分类
开发语言
ZengLiangYi4 小时前
TypeScript 项目配置:tsconfig、ESM、路径别名
javascript·typescript·aigc
chase_my_dream4 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
晓13134 小时前
【Cocos Creator 3.x】篇——第二章 入门
前端·javascript·游戏引擎