资讯详情

仓颉-JavaScript 互操作库 cangjie_js_interop:系统架构、编译构建与跨语言调用实战

📅 2026/10/14 17:33:20 | 华诺云谱 👁 阅读
仓颉-JavaScript 互操作库 cangjie_js_interop:系统架构、编译构建与跨语言调用实战
OpenHarmony【免费下载链接】cangjie_js_interop项目地址https://gitcode.com/Cangjie/cangjie_js_interop点击查看免费下载本篇围绕开源仓库 cangjie_js_interop 的中文 README 展开完整解析仓颉Cangjie与 JavaScript 互操作库的系统架构、核心类型、线程协同、引用管理与异常传播机制并结合仓库源码与用户手册给出可在 OpenHarmony 场景落地的集成步骤、完整代码示例与cjpm编译构建指南。读完后你将掌握如何在仓颉工程中接入互操作库、调用 JavaScript 函数、注册仓颉函数被 JS 回调并能从源码层面理解每一次跨语言调用背后的实现路径。一、库定位与仓库结构仓颉-JavaScript 互操作库用于提供仓颉调用 JavaScript 的跨语言能力即仓颉调用 JavaScript 库的互操作接口。其设计目标是给开发者提供兼顾开发效率与运行效率的跨语言解决方案提供简洁的开发范式降低跨语言互通开销。当前本仓开放的接口仅支持鸿蒙场景且只支持从仓颉侧调用 JavaScript、以及 JavaScript 回调仓颉。仓库目录结构非常精简摘自 README_zh.mdCangjie/cangjie_js_interop ├── src # 仓颉互操作库 ├── doc # 互操作库文档 ├── figures # 本README需要使用的图片src/库的全部仓颉实现包括runtime.cjJSRuntime、context.cjJSContext/scope 管理、value.cjJSValue 与基本类型、function.cjJSFunction/JSCallInfo/回调桩、heap.cj强引用基类 JSHeapObject、array.cj/object.cj/map.cj/string.cj容器与字符串类型、script.cjJSScript 与代码缓存、uv.cjlibuv 线程循环、napi_cffi.cjJSVM-API 的 C 互操作声明等doc/js_interop_api_docs.md完整的 API 参考文档doc/js_interop_user_manual.md用户手册包含类型映射、生命周期说明与更多示例。二、系统架构整体架构分为接口层与框架层两部分底层依赖JSVM-API部件。接口层开发者直接面对的五个核心类型互操作库 js_interop开发者通过调用互操作库提供的 API实现跨语言参数传递、函数调用等能力JSRuntime表示 JavaScript 运行时实例。每个 JavaScript 运行时对应一个 JSRuntime 实例提供创建 JSRuntime 的方法以及获取与该 JSRuntime 关联的执行上下文JSContextJSContext表示与一个 JSRuntime 关联的执行上下文保存全局对象并提供模块加载、JSValue 访问等相关方法JSCallInfo表示 JavaScript 调用仓颉方法时的调用信息主要用于获取参数数量和参数列表JSCodeCache表示一段 JavaScript 代码的缓存主要用于加速下次编译JSValue表示一个 JavaScript 对象。在仓颉与 JavaScript 互操作时它用于封装和传递 JavaScript 数据对象。当在仓颉侧需要获取 JSValue 中的值时需要用 JSValue 提供的方法将其转换为具体类型的实例比如JSNumber、JSString、JSBoolean等反之当需要把数据传递给 JavaScript 时可以利用 JSContext 提供的方法将仓颉对象封装成 JSValue 实例。框架层六项关键能力运行时管理提供创建 JavaScript 运行时的能力。跨语言函数调用在仓颉调用 JavaScript 场景通过 C 语言互操作 JavaScript 运行时接口实现调用在 JavaScript 调用仓颉场景通过互操作接口注册仓颉函数到 JavaScript 运行时然后 JavaScript 运行时通过 C 语言互操作调用仓颉函数。跨语言类型转换互操作库主要提供JSUndefined、JSNull、JSBoolean、JSNumber、JSString、JSObject、JSArray、JSMap、JSFunction等类型并提供接口实现 JavaScript 类型和仓颉类型的相互转换映射关系见下文“类型映射”一节。跨语言异常处理仓颉调用 JavaScript 时JS 侧异常未捕获会转换成仓颉异常传播到仓颉侧JavaScript 调用仓颉时仓颉侧异常未捕获会转换成 JS 异常传播到 JavaScript 侧。跨语言引用管理提供安全引用机制在仓颉侧使用仓颉对象映射 JavaScript 运行时对象保证这些 JavaScript 对象在跨语言调用时不会被垃圾回收。跨语言线程协同仓颉提供用户态线程运行时会将仓颉线程调度到系统线程上执行仓颉默认不绑定特定系统线程而互操作逻辑要求运行在 JavaScript 运行时绑定的系统线程上。因此互操作库提供接口可以把执行切换到 JavaScript 线程上完成。依赖部件说明JSVM-API 提供调用 JavaScript 虚拟机接口的能力本库通过 C 语言互操作调用 JSVM 提供的接口完成全部互操作功能对应的 C 函数声明集中在 src/napi_cffi.cj如OH_JSVM_CreateVM、OH_JSVM_CompileScript、OH_JSVM_CallFunction等Cforeign 声明以及JSVM_Status、JSVM_ValueType等基础类型。三、快速上手把互操作库集成到仓颉工程以下流程摘自 用户手册“快速上手”章节通过示例介绍如何在 DevEco 中使用互操作库实现仓颉与 JavaScript 的互操作调用。通用步骤为在 DevEco 中创建仓颉工程将互操作库代码复制到工程的顶层目录下形成手册中所示的TPC-HMOS-feature_js_interop目录结构在src-main-cangjie-cjpm.toml文件中添加依赖... [dependencies.tpc] path ../../../../TPC-HMOS-feature_js_interop version 1.0.0 ...在src-main-cangjie下新建仓颉文件以test.cj为例在test.cj中编写互操作代码见下文两个场景修改src-main-cangjie-index.cj使用test函数... evt message test() ...编译运行后点击按钮即可显示在 JS 侧计算的结果。场景一仓颉调用 JavaScript使用仓颉开发时通过跨语言互操作调用 JavaScript 库复用 JavaScript 丰富的库生态。test.cj示例其中第 13~15 行即为被调用的 JS 代码package ohos_app_cangjie_entry import js_interop.* func test() { // 创建JS运行时 let runtime JSRuntime.create() // 获取互操作上下文 let context runtime.mainContext context.newScope { // 加载JS代码到JS运行时 context.evaluate( function addNumber(a, b) { return a b; } ) // 获取加载至全局的JS函数 let addNumber context.global[addNumber].asFunction() // 调用JS函数 let result addNumber.globalCall([1, 2]) let retValue result.toNumberInt64() return 1 2 ${retValue} } }该示例覆盖了完整调用链evaluate把 JS 源码编译执行并挂到全局对象上context.global[addNumber]取回JSValue后经asFunction()转为JSFunction再用globalCall([1, 2])以undefined为 this 调用最后把返回值JSValue转成仓颉Int64。场景二仓颉调用 JavaScript 后回调仓颉在使用 JavaScript 开发时通过跨语言互操作调用仓颉开发的代码模块发挥仓颉高性能高并发优势提升应用性能体验。注意目前只支持仓颉调用 JavaScript 后回调仓颉。示例中第 14 行即为被调用的仓颉代码package ohos_app_cangjie_entry import js_interop.* // 定义被调用接口 func addNumber(context: JSContext, callInfo: JSCallInfo): JSValue { // 从JSCallInfo获取参数列表 let arg0: JSValue callInfo[0] let arg1: JSValue callInfo[1] // 把JSValue转换为仓颉类型 let a: Float64 arg0.toNumberFloat64() let b: Float64 arg1.toNumberFloat64() // 实际仓颉函数行为 let value a b // 把结果转换为JSValue let result: JSValue context.number(value).toJSValue() // 返回JSValue return result } func test() { // 创建JS运行时 let runtime JSRuntime.create() // 获取互操作上下文 let context runtime.mainContext context.newScope { // 注册到global即全局对象 context.global[add] context.function(addNumber) // 从仓颉侧执行JS的调用代码 let retValue context.evaluate(add(1, 2)).toNumberInt64().toString() return 1 2 ${retValue} } }关键点被回调的仓颉函数签名必须是(JSContext, JSCallInfo) - JSValue通过JSCallInfo的[]运算符按索引取 JS 实参处理完毕后必须构造并返回一个JSValue。四、核心接口与源码级原理本节基于 src/ 目录下的实现把 README 架构图中每一项能力落到具体代码路径上。4.1 准备互操作环境JSRuntime 与 JSContext用户手册给出的标准三步流程是通过JSRuntime的create函数创建一个JSRuntime实例通过JSRuntime实例的mainContext属性获取JSContext实例在JSContext实例的newScope函数内通过JSContext的函数接口进行一系列跨语言互操作。需要注意的是不同的JSRuntime实例中数据是互相隔离的。从源码结构看src/runtime.cj#L57-L69 的JSRuntime.create()完成三件事首次调用时通过Once机制执行一次全局初始化OH_JSVM_InitJSVMGlobalInitTask并过滤重复初始化的错误码见 src/runtime.cj#L50-L55 与checkForInit通过UvLoop.newLoop()创建一个独立的 libuv 事件循环线程见 src/uv.cj#L74-L86再向该线程postTask在 JS 线程内部调用OH_JSVM_CreateVM与OH_JSVM_OpenVMScope完成 VM 创建src/runtime.cj#L71-L83用原子计数器自旋等待任务完成后返回实例。mainContext是惰性初始化的只读属性src/runtime.cj#L144-L160首次访问时同样把createContext()投递到 libuv 线程执行其中调用OH_JSVM_CreateEnv创建环境句柄再由JSContext.createContext将JSContext注册进全局映射表allContext_以 env 指针为键src/context.cj#L49-L63这一步正是 JS 回调仓颉时能按 env 找回上下文的基础。4.2 newScope 与跨语言线程协同newScopeT(task: () - T): T是所有互操作操作的“执行域”其源码实现src/context.cj#L174-L202清晰地体现了 README 所说的跨语言线程协同若当前仓颉线程恰好就是 JS 运行时绑定的 libuv 线程loop_.isOnRunningThread()则直接调用createScopes(task)否则先把任务postJSTask投递到 libuv 线程执行当前线程自旋等待结果并把 JS 线程中捕获的异常消息回传给调用线程重新抛出。createScopessrc/context.cj#L123-L144内部调用OH_JSVM_OpenHandleScope/OH_JSVM_CloseHandleScope开启句柄作用域并在Exception/Error两条路径上都保证作用域被关闭、scopeDepth_回退——这就是“跨语言弱引用只在 scope 作用域内有效”的底层依据。线程亲和性还体现在JSContext.env属性上src/context.cj#L36-L47若在非 libuv 线程直接访问底层 env会抛出Should be run in the libuv thread.。对开发者而言只要把互操作代码写在newScope闭包里线程切换由库自动完成而任务投递底层是uv_async_send唤醒 libuv 循环执行待办任务src/uv.cj#L154-L169。4.3 JSValue 与跨语言类型转换JSValue是所有跨语言值的统一载体实现于 src/value.cj。它提供两组 API类型判定typeof()以及isUndefined()、isNull()、isNumber()、isString()、isObject()、isFunction()、isArray()、isMap()等全部通过对应的OH_JSVM_Is*C 接口实现类型转换/取值asFunction()、asObject()、asString()等“断言式”转换类型不符时抛出带实际类型的异常如 src/value.cj#L296-L301 的asFunction以及toBoolean()、toNumberInt32/Uint32/Int64/Float64()、toString()等取值接口。反向转换则由JSContext的工厂方法完成均直接映射到 JSVM 的 Create 系列接口src/context.cj#L244-L365number(Int32/UInt32/Int64/Float64)→OH_JSVM_CreateInt32/CreateUint32/CreateInt64/CreateDoublestring(String)→OH_JSVM_CreateStringUtf8返回强引用类型JSStringobject()/object(properties)→OH_JSVM_CreateObjectarray()/array(data)→OH_JSVM_CreateArray(CreateArrayWithLength)map()/map(pairs)→OH_JSVM_CreateMapfunction(lambda)→JSFunction.createFunc见 4.5 节。用户手册给出的完整类型映射表如下支持情况以用户手册为准JavaScript 类型仓颉引用typeof 类型支持情况UndefinedJSUndefinedJSType.UNDEFINED已支持NullJSNullJSType.NULL已支持BooleanJSBooleanJSType.BOOLEAN已支持NumberJSNumberJSType.NUMBER已支持StringJSStringJSType.STRING已支持Symbol-JSType.SYMBOL暂不支持ObjectJSObjectJSType.OBJECT已支持ArrayJSArrayJSType.OBJECT已支持ArrayBuffer-JSType.OBJECT暂不支持Date-JSType.OBJECT暂不支持MapJSMapJSType.OBJECT已支持Set-JSType.OBJECT暂不支持Promise-JSType.OBJECT暂不支持RegExp-JSType.OBJECT暂不支持FunctionJSFunctionJSType.FUNCTION已支持Class-JSType.FUNCTION暂不支持--JSType.EXTERNAL暂不支持BigInt-JSType.BIGINT暂不支持各类型的详细 API 可参见 API 文档。容器类的操作接口也有对应实现JSArray的size/deleteAt/[]读写元素src/array.cj其中越界读取返回undefined、负索引抛异常JSObject的hasProperty/deleteProperty/getPropertyNames/[]src/object.cj键支持String、Int64、JSString三种JSKeyable类型JSMap的hasProperty/keys/[]src/map.cj。4.4 执行 JavaScript 代码与代码缓存在newScope内通过evaluate函数执行 JavaScript 代码签名与源码实现为src/context.cj#L375-L385public func evaluate(src: String, codeCache!: JSCodeCache JSCodeCache()): JSValue其内部流程是先把源码封装为 JS 字符串再调用OH_JSVM_CompileScript携带可选的代码缓存数据编译成脚本然后OH_JSVM_RunScript执行并把返回值包装为JSValue。执行过程中若出现 JavaScript 报错和异常互操作库会将之转换为仓颉异常抛出代码中定义的全局变量和函数声明会进入当前JSContext的全局对象可被后续代码使用但需保证被依赖的数据已被执行或创建过。代码缓存用于加速重复编译JSCodeCache只是data指针加length的轻量结构src/script.cj#L11-L24。当传入的代码与缓存校验一致时使用缓存加速编译否则直接编译。此外JSScript可通过createCodeCache()OH_JSVM_CreateCodeCache从已编译脚本生成缓存JSScriptBuilder还提供setCompileMode、setCompileCache/setNoneCache、setEnableSourceMap、setOrigin等编译选项的链式配置能力src/script.cj#L48-L116。用户手册中一个典型的“注册全局函数 执行代码”示例含异常传播演示func test() { let runtime JSRuntime.create() let context runtime.mainContext context.newScope { // 创建console.log函数 context.global[console] context.object([ (log, context.function { context, info let message info.args[0].toString() Hilog.info(0, console.log, message) context.undefined() }) ]) // 执行JS代码 context.evaluate(console.log(hello, world!)) context.evaluate(console.log1(hello, world!)) // JS异常传播到仓颉抛出log1不存在 } }4.5 跨语言函数调用双向调用链与异常传播仓颉 → JavaScript。JSFunction提供两种调用入口src/function.cj#L161-L170call(thisArg: JSValue, args: ArrayToJSValue)指定 this 调用globalCall(args: ArrayToJSValue)以undefined为 this 的快捷调用。二者最终都进入innerCall把ToJSValue参数逐一转换后调用OH_JSVM_CallFunction。参数类型只要实现了ToJSValue接口即可传入仓颉的String、Int64等已有扩展实现见 src/object.cj#L11-L21。JavaScript → 仓颉。被回调的仓颉函数被定义为public type JSLambda (JSContext, JSCallInfo) - JSValuesrc/function.cj#L52。context.function(lambda)注册时的底层流程src/function.cj#L125-L159lambda 存入上下文的sharedLambda_Slab 分配器得到一个lambdaId构造JSVM_Callback指向 C 桩函数jsFunctionCallbackStub携带lambdaId调用OH_JSVM_CreateFunction创建 JS 函数通过OH_JSVM_AddFinalizer注册析构回调jsFunctionFinalizerStub在 JS 侧函数被回收时释放对应的 C 内存与 Slab 槽位。JS 端每次调用触发回调桩jsFunctionCallbackStubsrc/function.cj#L69-L91按 env 从allContext_找回JSContext→ 开启可逃逸作用域newEscapeScope使返回值可逃逸出回调作用域→JSCallInfo.parseCallInfo通过OH_JSVM_GetCbInfo解析出参数列表、this 对象与lambdaIdsrc/function.cj#L9-L50→ 用lambdaId从 Slab 取出 lambda 并执行 → 返回其JSValue。JSCallInfo对外暴露size参数数量、args参数列表、thisArgthis 对象以及[]索引运算符与 README 中“主要用于获取参数数量和参数列表”的描述一致。跨语言异常处理的双向闭环JS → 仓颉checkAndHandleErrorsrc/runtime.cj#L122-L142在OH_JSVM_*调用失败且处于 pending 异常状态时先OH_JSVM_GetAndClearLastException取出异常对象再读取其stack属性把完整 JS 堆栈包装成仓颉Exception抛出仓颉 → JS回调桩中捕获到仓颉Exception/Error时调用throwExceptionToJSsrc/function.cj#L54-L67把异常信息与格式化后的仓颉堆栈Cangjie info\n\t at ...通过OH_JSVM_ThrowError抛入 JS 运行时并返回undefined。用户手册中的add(1, 2)示例即演示了“仓颉异常传播到 JS 再传播回仓颉抛出”的完整路径。4.6 跨语言引用管理与生命周期互操作类型分为两类生命周期摘自用户手册“互操作类型的生命周期”跨语言弱引用持有 JavaScript 对象的弱引用不会阻止其回收只在 scope 作用域内有效。对应类型JSValue、JSUndefined、JSNull、JSBoolean、JSNumber。它们只是对JSVM_Value句柄的薄封装见 src/value.cj#L10-L118跨语言强引用持有强引用保证对象不被回收可跨 scope 使用在上下文存活期间有效。对应类型JSString、JSObject、JSArray、JSMap、JSFunction。这些类型统一继承自JSHeapObject进行生命周期管理。从源码结构看src/heap.cj 的JSHeapObject在构造时即调用OH_JSVM_CreateReference创建引用计数型JSVM_Ref各强引用子类的析构函数如 src/string.cj#L14-L16调用context_.releaseRef(ref_)把引用加入待删除队列toDeleteRefs_由于OH_JSVM_DeleteReference必须在 JS 线程执行真正的删除被延迟到 libuv 线程的doAsyncTask中统一批量执行src/context.cj#L81-L95 与 src/context.cj#L109-L120——这正是 README 所说“保证 JavaScript 对象在跨语言调用时不会被垃圾回收”的具体机制。五、编译构建构建准备开发者需要 Linux 平台的cangjie SDK然后执行对应SDK的envsetup脚本确保SDK被正确配置。构建步骤克隆本项目git clone 时使用仓库地址https://gitcode.com/Cangjie/cangjie_js_interop。通过根目录下的 cjpm.toml 编译仓颉-JavaScript 互操作库cjpm build --release当前支持debug、release两种编译类型开发者需要通过--release或者--debug指定默认安装到target目录下支持通过--target-dir指定安装目录。当前编译产物目录结构为target/ └── release └── js_interop ├── incremental-cache.json ├── js_interop.cjo └── libjs_interop.socjpm.toml 关键配置解读cjpm.toml 中声明了本库的构建参数与上述构建说明一一对应[package]包名js_interop、版本1.0.0、cjc-version 0.53.21要求对应的仓颉编译器版本、output-type dynamic动态库对应产物libjs_interop.so、src-dir ./src、target-dir ./target[profile.build]开启增量编译incremental true[profile.customized-option]debug使用-g -Woff all带调试信息release使用--fast-math -O2 -s -Woff allO2 优化并 strip[target.aarch64-linux-ohos]针对 OpenHarmony aarch64 目标配置了编译选项与bin-dependencies路径依赖${DEVECO_CANGJIE_HOME}、${DEVECO_OH_NATIVE_HOME}等环境变量这些变量由 SDK 的envsetup脚本导出。从这一目标配置可以推断库的最终运行形态面向 OpenHarmony 设备与 README“当前本仓开放的接口仅支持鸿蒙场景”的表述相互印证。六、适用约束与相关资源约束以当前仓库为准目前仅支持在Linux 平台编译和使用并且仅支持仓颉编译器的cjnative 后端开放的接口仅支持鸿蒙OpenHarmony场景调用方向上目前只支持仓颉调用 JavaScript以及 JavaScript 回调仓颉不支持 JS 主动发起的完整交互类型层面Symbol、ArrayBuffer、Date、Set、Promise、RegExp、Class、BigInt等类型暂不支持详见上文类型映射表。相关资源API 参考doc/js_interop_api_docs.md用户手册含类型映射、生命周期与更多示例doc/js_interop_user_manual.md相关仓arkcompiler_jsvmJSVM-API 部件为互操作库提供 JavaScript 虚拟机接口开源协议本项目基于 Apache-2.0 with Runtime Library Exception 和 Attribution 4.0 International。参与贡献欢迎广大开发者贡献代码、文档等具体的贡献流程和方式请参见仓颉社区CangjieCommunity的参与贡献文档。结语cangjie_js_interop 用一套“JSRuntime → JSContext → newScope”的简洁范式把 JSVM-API 的 C 接口封装成类型安全、带完整引用/线程/异常管理的仓颉 API。理解本文梳理的 scope 机制、libuv 线程协同与JSVM_Ref引用队列之后你可以按用户手册的示例快速在 OpenHarmony 工程中完成仓颉与 JavaScript 的混合开发并借助源码定位每一次跨语言调用的精确落点。赞分享OpenHarmony【免费下载链接】cangjie_js_interop项目地址https://gitcode.com/Cangjie/cangjie_js_interop点击查看免费下载相关推荐仓颉-JavaScript 互操作库跨语言调用的架构设计与实战指南仓颉 JavaScript 互操作库跨语言调用的架构设计与实战指南 本文以仓颉 JavaScript 互操作库cangjie_js_interop的 REOpenHarmonycangjie_python_interop仓颉-Python 互操作库的架构、构建与使用指南cangjie_python_interop仓颉 Python 互操作库的架构、构建与使用指南 cangjie_python_interop仓颉 Pytho开发工具在仓颉中创建并操作JavaScript对象Cangjie/cangjie_js_interop的JSObject、JSArray与JSMap实战指南在仓颉中创建并操作JavaScript对象Cangjie/cangjie_js_interop的JSObject、JSArray与JSMap实战指南 仓颉 JOpenHarmony创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑