如何用C语言互操作桥接JSVM虚拟机完整拆解仓颉js_interop系统架构napi_cffi源码剖析【免费下载链接】cangjie_js_interop项目地址: https://gitcode.com/Cangjie/cangjie_js_interop仓颉/cangjie_js_interop 是一个面向鸿蒙场景的仓颉与 JavaScript 跨语言互操作库它通过C 语言互操作CFFI桥接JSVM JavaScript 虚拟机让仓颉应用可以直接创建 JS 运行时、执行 JS 代码、调用 JS 函数并支持 JS 回调仓颉函数。本文基于源码完整拆解它的napi_cffi 接口层与系统架构帮助新手快速理解跨语言互操作是如何落地的。 先搞懂js_interop 解决什么问题在 OpenHarmony 应用开发中仓颉和 JavaScript 经常需要混合开发仓颉调用 JavaScript复用 JS 丰富的库生态把现成的 JS 模块当作依赖来用JavaScript 回调仓颉在 JS 侧触发高性能的仓颉代码模块发挥仓颉高并发优势。互操作库的核心价值就是在两者之间架起一座桥跨语言传参、跨语言函数调用、跨语言类型转换、跨语言异常处理、引用管理与线程协同全部封装成简洁的 API开发者无需关心底层 C 接口细节。️ 系统架构接口层 框架层 JSVM-API 三层结构图示在 DevEco 中选择 [Cangjie] Empty Ability 模板创建仓颉工程这是使用 js_interop 互操作库的第一步。整体架构分为三层层次组件职责接口层js_interopJSRuntime/JSContext/JSCallInfo/JSCodeCache/JSValue面向开发者的跨语言 API创建运行时、获取上下文、封装 JS 数据对象框架层cangjie_js_interop运行时管理、跨语言函数调用、类型转换、异常处理、引用管理、线程协同接口层能力的真正实现内部全部走 C 互操作调用 JSVMJSVM-APIOH_JSVM_*C 函数JavaScript 虚拟机提供的 C 接口是整条链路的地基几个关键概念对应关系JSRuntime一个 JS 虚拟机实例对应 C 层的JSVM_VM指针JSContext与运行时关联的执行上下文对应JSVM_Env指针保存全局对象模块加载、值访问都发生在它的 Scope 里JSCallInfoJS 回调仓颉方法时的调用信息用来取参数个数与参数列表JSValue跨语言通用货币任何 JS 数据在仓颉侧都用它承载再按需转成JSNumber、JSString、JSBoolean等具体类型。 napi_cffi 源码剖析C 互操作桥如何对接JSVM整个库最底层的桥梁就是 src/napi_cffi.cj 中约 100 个 C 函数的foreign声明块。它的作用可以概括为三件事1️⃣ 把 C 的不透明指针变成仓颉类型在 src/napi_cffi.cj 中JSVM_VM、JSVM_Env、JSVM_Value等类型全部被声明为指向空 C 结构的指针如type JSVM_VM CPointerJSVM_VM__。这种不透明句柄模式意味着仓颉侧完全不解析 JSVM 内部结构只传递指针、按 C ABI 约定调用函数从而天然与 JSVM 二进制兼容。2️⃣ 用C结构体精确复刻 C 的内存布局JSVM_Statussrc/napi_cffi.cj、JSVM_CreateVMOptions、JSVM_Callback等结构体加上C标注后与 C 头文件中的定义逐字段对齐。所有OH_JSVM_*函数统一返回JSVM_Status库内通过check()判断状态码非 OK 就抛出仓颉异常——这就是跨语言异常处理的入口。3️⃣ 覆盖 JSVM 的完整能力面foreign块src/napi_cffi.cj按功能分组声明了虚拟机与上下文生命周期OH_JSVM_Init→OH_JSVM_CreateVM→OH_JSVM_CreateEnv→ 打开/关闭 ScopeJS 值创建与读取OH_JSVM_CreateStringUtf8、OH_JSVM_GetValueInt64等是类型转换的基础脚本编译执行OH_JSVM_CompileScript、OH_JSVM_RunScript、OH_JSVM_CreateCodeCache代码缓存加速二次编译函数调用与回调OH_JSVM_CallFunction、OH_JSVM_CreateFunction、OH_JSVM_GetCbInfo。 跨语言函数调用的双向链路仓颉 → JavaScript调用方向源码在 src/runtime.cj 中可以看到完整流程JSRuntime.create()先执行全局OH_JSVM_Init用Once保证只初始化一次再在 libuv 线程上OH_JSVM_CreateVM创建虚拟机并打开 VM Scope随后 src/runtime.cj 的mainContext属性通过OH_JSVM_CreateEnv创建环境包装成JSContext。之后在上下文 Scope 中evaluateJS 代码、从全局对象取出 JS 函数、CallFunction调用返回值以JSValue回到仓颉侧。JavaScript → 仓颉回调方向关键在 src/function.cj 的jsFunctionCallbackStub这是一个 C 函数指针通过OH_JSVM_CreateFunction注册给 JSVM。当 JS 侧调用该函数时JSVM 反向调用这个 stub它再做四步由env反查对应的JSContext通过JSCallInfo.parseCallInfosrc/function.cj把 C 侧的JSVM_CallbackInfo解析成参数数组按lambdaId找到预先注册的仓颉闭包JSLambda并执行若仓颉侧抛异常throwExceptionToJSsrc/function.cj把它转成 JS 异常向 JS 侧传播——双向异常处理由此闭环。线程协同仓颉线程默认不绑定系统线程而 JSVM 要求操作必须发生在绑定线程上。库内部用 src/uv.cj 的UvLoop封装 libuv 事件循环JSContext在访问env前会检查当前线程src/context.cj异步任务则通过postTask投递回 JS 线程执行保证句柄安全。 快速上手3 步跑通第一次互操作创建仓颉工程在 DevEco 中选择[Cangjie] Empty Ability模板仓库可通过git clone https://gitcode.com/Cangjie/cangjie_js_interop获取源码并放入工程顶层目录添加依赖把互操作库加入cjpm.toml的依赖项如下图编写调用代码新建仓颉文件后按 doc/js_interop_user_manual.md 的示例JSRuntime.create()建运行时 →runtime.mainContext取上下文 →context.evaluate(...)加载 JS →context.global[addNumber].asFunction()取函数 →globalCall([1, 2])调用并取回结果。完整 API 细节可查阅 doc/js_interop_api_docs.md。运行效果示意按钮点击触发仓颉函数内部完成仓颉→JS 互操作调用 源码导读各模块文件分工模块文件行数职责src/napi_cffi.cj634C 互操作接口定义JSVM 类型、结构体、foreign 函数声明src/runtime.cj177JSRuntimeVM 创建、销毁、堆统计、状态码检查src/context.cj392JSContextScope 管理、异步任务、引用回收、全局对象访问src/value.cj405JSValue及 JS 基础类型Boolean/Number/String封装src/function.cj194JS 函数调用与仓颉回调 stub、跨语言异常转换src/script.cj152JSScript/JSScriptBuilder脚本编译、代码缓存src/uv.cj242libuv 事件循环封装跨语言线程协同的核心src/slab.cj185小对象内存池承载仓颉闭包与异步回调辅助模块还包括 src/string.cj、src/array.cj、src/map.cj、src/object.cj对应 JSString/JSArray/JSMap/JSObject 类型以及 src/heap.cj、src/log.cj。❓ 常见问题FAQQ1napi_cffi.cj 和 Node.js 的 N-API 是一回事吗命名上借鉴了 N-API 的设计思想C 语言函数指针 不透明句柄但对接的是 JSVM 的OH_JSVM_*C 接口不是 Node 的 N-API。Q2为什么仓颉侧拿不到 JSVM 的内部结构这是刻意为之。C空结构 指针的不透明设计让仓颉侧与 JSVM 实现完全解耦JSVM 升级时只要 C ABI 不变互操作库就无需修改。Q3目前支持哪些平台和方向当前仅支持 Linux 平台编译cjpm build --release运行时面向鸿蒙场景调用方向支持仓颉调用 JS 后 JS 回调仓颉编译配置见根目录 cjpm.toml。Q4JS 侧异常会导致仓颉崩溃吗不会。状态码检查checkAndHandleError会把挂起的 JS 异常取出并转换为仓颉Exception反方向则由throwExceptionToJS把仓颉异常包装为 JS Error两侧都有兜底。✅ 总结仓颉/cangjie_js_interop 用三层架构把跨语言调用的复杂性层层收敛napi_cffi.cj 的 C 互操作声明对接 JSVM框架层实现函数调用、类型转换、异常与引用管理接口层则只暴露JSRuntime/JSContext/JSValue等寥寥几个类。理解了这条C 指针 → CFFI → 仓颉对象的链路你就掌握了整个互操作库的骨架后续阅读任意模块源码都会事半功倍。【免费下载链接】cangjie_js_interop项目地址: https://gitcode.com/Cangjie/cangjie_js_interop创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
