鸿蒙JSVM-API深度解析:C++与JS跨语言交互实战指南

📅 2026/7/16 7:53:33
鸿蒙JSVM-API深度解析:C++与JS跨语言交互实战指南
1. 项目概述与核心价值最近在捣鼓鸿蒙5.0的Next版本发现一个挺有意思的东西JSVM-API。简单来说它就像是在鸿蒙的JS应用和底层的C/C代码之间搭了一座桥。这玩意儿对于那些想在鸿蒙上做点高性能、或者需要复用大量现有C/C库的开发者来说简直是福音。你想想JS写UI和业务逻辑很方便但碰到音视频编解码、复杂图像处理、或者需要直接操作硬件的场景性能可能就有点捉襟见肘了。这时候如果能调用用C/C写的、经过高度优化的原生模块那体验和效率的提升就不是一点半点了。JSVM-API就是干这个的它让你能在JS环境里相对安全、高效地调用C/C函数还能在两种语言之间传递复杂的数据。这不仅仅是“能调用”更关键的是它提供了一套相对规范的交互机制包括内存管理、类型转换、异常处理等让这种跨语言调用不再是“黑魔法”而是有章可循的工程实践。2. JSVM-API核心概念与交互模型解析2.1 JSVM-API是什么不是什么首先得明确JSVM-API不是一个新的编程语言也不是一个独立的运行时。它是鸿蒙ArkTS/JS运行时特别是针对Next版本提供的一组原生NativeAPI接口。这套API的核心目标是建立JavaScript或TypeScript最终编译为ArkTS Bytecode与C/C原生代码之间的互操作通道。你可以把它理解为一个精心设计的“外交协议”和“翻译官”。它“是”什么一套C头文件Header Files主要包含jsvm_api.h等定义了所有可供C/C代码使用的数据结构、函数原型和宏。一个运行时桥梁它在JS引擎如ArkTS引擎和你的原生模块之间管理调用栈、内存和生命周期。类型转换系统自动或在开发者指导下在JS的number,string,object,ArrayBuffer等类型与C/C的int,double,char*,void*等类型之间进行转换。错误处理机制提供将C/C侧的异常或错误以JSError对象的形式抛回JS环境的能力。它“不是”什么不是FFIForeign Function Interface的简单封装虽然目标类似但JSVM-API深度集成在鸿蒙的ArkTS运行时中考虑了鸿蒙应用模型、线程模型和安全沙箱不是通用的跨平台FFI。不是自动绑定生成器它不会像一些工具那样自动扫描你的C头文件然后生成JS绑定代码。你需要手动编写适配层代码通常是一个C接口明确地描述每个要暴露的函数如何转换参数和返回值。不是直接的内存共享JS和C/C有各自独立的内存空间。JSVM-API在传递数据时多数情况下涉及拷贝如字符串。对于大数据如图像帧它提供了ArrayBuffer或NativeBindingObject等机制来减少拷贝开销但内存管理责任需要开发者清晰界定。2.2 交互模型JS如何“看见”C/C理解交互模型是正确使用JSVM-API的关键。整个过程可以类比为一次远程过程调用RPC只不过“远程”是在同一个进程内的不同语言环境。核心流程如下原生模块编译你的C/C代码我们称之为“原生模块”被编译成一个动态链接库.so文件。这个库需要链接鸿蒙的NDK中提供的JSVM相关库。模块注册在原生模块的入口处你需要实现一个特定的函数例如Init函数。在这个函数里你使用JSVM-API提供的函数向JS运行时“注册”你这个模块并声明你要暴露哪些函数、属性或类给JS。JS侧加载在你的ArkTS/JS代码中使用鸿蒙提供的import或load机制具体语法可能因版本而异例如import nativeModule from libmyNative.so来加载这个.so文件。运行时系统会找到并执行你模块中的Init函数。函数调用注册成功后你在JS中就可以像调用普通JS函数一样调用你暴露的原生函数了。例如// ArkTS/JS 侧 import nativeModule from libmyNative.so; let result nativeModule.add(1, 2); // 调用C函数 console.log(Result from C: ${result});参数与返回值的“翻译”当你调用nativeModule.add(1, 2)时JS运行时将参数1和2封装成JSVM内部表示。通过JSVM-API桥梁调用到你C模块中对应的add函数。在你的Cadd函数实现里你需要使用JSVM-API如JsValueToNumber将传入的JSVM参数句柄JsiValueRef转换为C的int。执行加法运算。使用JSVM-API如NumberToJsValue将C的int结果转换回JSVM值句柄并返回。JS运行时接收这个句柄将其转换回JS的number类型赋值给result。线程模型注意事项默认情况下JS调用原生函数发生在JS线程通常是UI线程。如果你的C/C函数执行耗时操作如大量计算、文件IO、网络请求会阻塞JS线程导致界面卡顿。因此对于耗时操作最佳实践是在C/C侧创建独立的工作线程pthread来执行任务。使用JSVM-API提供的异步回调机制如JsiCreateAsyncWork在工作线程完成后将结果回调到JS线程并触发JS侧的Promise resolve或callback。注意跨线程传递JSVM值句柄JsiValueRef是危险的因为它们与创建它们的线程上下文绑定。必须使用JsiCreateReference创建持久化引用并在跨线程传递后在正确的线程上下文中使用JsiAcquireValue和JsiReleaseValue来安全地访问。这是内存泄漏和崩溃的高发区。3. 核心API详解与内存管理要诀3.1 关键数据结构与API分类JSVM-API围绕几个核心数据结构展开JsiRuntimeHandle代表一个JS运行时环境。你的原生模块通常从回调函数中获取到当前调用的运行时句柄。所有后续的API调用大多需要这个句柄作为第一个参数因为它指明了操作发生在哪个JS引擎实例中。JsiValueRef这是最重要的类型它是一个指向JS值的“不透明句柄”opaque handle。无论是JS传来的参数还是你要返回给JS的值都以JsiValueRef的形式在C/C侧存在。你不能直接操作它内部的数据必须通过JSVM-API函数来查询其类型、提取其值或创建新的值。JsiPropertyDescriptor用于描述你要暴露给JS的对象属性或函数。你需要填充这个结构体指定属性名、getter/setter、函数指针、属性特性可写、可枚举、可配置等。API大致可以分为以下几类运行时与上下文管理如JsiGetCurrentRuntime。值类型检查与转换JsiValueIsNumber,JsiValueIsString,JsiValueIsObject,JsiValueIsArray等用于类型判断。JsiValueToNumber,JsiValueToCString,JsiNumberToValue,JsiCreateStringUtf8等用于双向转换。对象与属性操作JsiCreateObject,JsiCreateArrayJsiSetProperty,JsiGetProperty,JsiHasPropertyJsiDefineProperty(用于定义更复杂的属性结合JsiPropertyDescriptor)函数相关JsiCreateFunction将C函数指针包装成JS可调用的函数。JsiCallFunction在C侧调用JS函数。错误处理JsiCreateError,JsiThrowError。异步工作JsiCreateAsyncWork,JsiQueueAsyncWork。内存与引用管理重中之重JsiCreateReference,JsiAcquireValue,JsiReleaseValue。3.2 内存管理从“自动”到“手动”的思维转变对于习惯JS自动垃圾回收GC的开发者来说使用JSVM-API最大的挑战之一就是内存管理。在C/C侧你需要手动管理JS值的生命周期否则极易导致内存泄漏或悬空指针。核心原则谁创建谁或约定方释放跨线程需持久化。1. 局部值的生命周期在原生函数被调用时传入的参数JsiValueRef和函数返回的JsiValueRef其生命周期通常由JSVM运行时管理。你不需要也不应该去释放它们。例如// C 侧函数实现 JsiValueRef MyAdd(JsiRuntimeHandle runtime, JsiValueRef thisVal, int argc, JsiValueRef argv[]) { if (argc 2 || !JsiValueIsNumber(argv[0]) || !JsiValueIsNumber(argv[1])) { // 创建错误并抛出 JsiValueRef error JsiCreateError(runtime, Invalid arguments); JsiThrowError(runtime, error); // 注意这里创建的error如果Throw成功运行时可能会接管其生命周期。 // 但最安全的做法是如果Throw后函数立即返回通常不需要手动释放。 return JsiCreateUndefined(runtime); } double a JsiValueToNumber(runtime, argv[0]); // argv[0], argv[1] 不需要释放 double b JsiValueToNumber(runtime, argv[1]); double sum a b; JsiValueRef result JsiNumberToValue(runtime, sum); // 创建的result将由运行时在JS侧接收后管理 return result; // 返回给运行时无需释放 }2. 持久化引用Persistent References如果你需要在原生函数调用结束后仍然保存某个JsiValueRef例如保存一个JS回调函数以备后续异步调用你就必须创建“持久化引用”。// 假设argv[0]是一个JS函数我们想保存它 JsiValueRef jsCallback argv[0]; // 错误直接保存句柄当本次JS调用结束jsCallback可能被GC回收变成悬空指针。 // someGlobalVariable jsCallback; // 正确创建持久化引用。 JsiRef ref JsiCreateReference(runtime, jsCallback, 1); // 第三个参数1表示强引用 // 将ref保存到你的模块上下文或全局结构体中 myModuleContext-callbackRef ref;当你后续需要用到这个JS函数时JsiValueRef callbackValue JsiAcquireValue(runtime, myModuleContext-callbackRef); if (callbackValue) { // 使用callbackValue调用JS函数 JsiValueRef args[] { JsiCreateStringUtf8(runtime, Async work done!) }; JsiValueRef result; JsiCallFunction(runtime, callbackValue, nullptr, args, 1, result); // 调用完成后释放Acquire得到的值 JsiReleaseValue(runtime, callbackValue); }非常重要JsiAcquireValue和JsiReleaseValue必须成对出现。Acquire告诉运行时“我现在要用了别GC它”。Release告诉运行时“我用完了”。即使你持有JsiRef在每次使用前也需要Acquire使用后Release。3. 字符串内存管理使用JsiValueToCString或JsiCreateStringUtf8时需要注意返回的char*指针的生命周期。JsiValueToCString返回的指针指向的内存可能是临时的。如果你需要长时间持有这个字符串必须立即将其拷贝到自己的缓冲区如std::string或malloc的内存。JsiCreateStringUtf8创建的字符串其内存在对应的JsiValueRef被GC回收时也会被管理。你不需要直接释放它。4. 异步工作与内存安全在异步回调中你经常需要将数据从工作线程传递回JS线程。传递JsiValueRef是危险的。标准做法是在工作线程启动前使用JsiCreateReference为需要的JS值创建持久化引用。将JsiRef它是一个整数或指针不依赖运行时上下文或你自定义的、包含必要原始数据如int,double, 拷贝的char*的结构体指针传递给工作线程。在工作线程完成后的完成回调这个回调在JS线程执行中使用JsiAcquireValue获取JS值并进行后续操作最后记得JsiReleaseValue和JsiDeleteReference。实操心得内存泄漏排查一个常见的检查清单是1) 每个JsiCreateReference是否都有配对的JsiDeleteReference2) 每个JsiAcquireValue是否都有配对的JsiReleaseValue3) 从JsiValueToCString得到的字符串如果需长期使用是否及时拷贝可以使用简单的引用计数日志或鸿蒙提供的性能分析工具来辅助定位。4. 完整实战从C模块到JS调用的全流程让我们通过一个具体的例子将一个C的图像处理函数模拟灰度化暴露给JS。假设我们的C库名为libimageProcessor.so。4.1 C侧原生模块开发第一步定义头文件 (image_processor.h)#ifndef IMAGE_PROCESSOR_H #define IMAGE_PROCESSOR_H #include cstdint #ifdef __cplusplus extern C { #endif // 暴露给JS的C接口函数声明 void* createProcessor(); void destroyProcessor(void* handle); bool processImage(void* handle, const uint8_t* inputData, int width, int height, uint8_t* outputData); #ifdef __cplusplus } #endif #endif这里使用extern C确保函数名在C编译后不被修饰name mangling以便JSVM能够正确找到。第二步实现C核心类 (image_processor.cpp)#include image_processor.h #include vector #include algorithm // 仅为示例实际可能使用OpenCV等库 class ImageProcessorImpl { public: bool process(const uint8_t* input, int w, int h, uint8_t* output) { // 简单的灰度化模拟 (R G B) / 3 // 假设输入输出都是 RGBA 格式每像素4字节 int totalPixels w * h * 4; for (int i 0; i totalPixels; i 4) { uint8_t r input[i]; uint8_t g input[i 1]; uint8_t b input[i 2]; uint8_t gray static_castuint8_t((r g b) / 3); output[i] gray; output[i 1] gray; output[i 2] gray; output[i 3] input[i 3]; // Alpha通道保持不变 } return true; } }; // C接口实现 extern C { void* createProcessor() { return new ImageProcessorImpl(); } void destroyProcessor(void* handle) { if (handle) { delete static_castImageProcessorImpl*(handle); } } bool processImage(void* handle, const uint8_t* inputData, int width, int height, uint8_t* outputData) { if (!handle || !inputData || !outputData || width 0 || height 0) { return false; } ImageProcessorImpl* processor static_castImageProcessorImpl*(handle); return processor-process(inputData, width, height, outputData); } }第三步编写JSVM适配层 (jsvm_adapter.cpp)这是最关键的一步负责“翻译”。#include image_processor.h #include jsvm_api.h #include cstring // 全局模块实例指针 static void* g_processorHandle nullptr; // 1. 暴露给JS的函数初始化处理器 static JsiValueRef NativeInitProcessor(JsiRuntimeHandle runtime, JsiValueRef thisVal, int argc, JsiValueRef argv[]) { if (g_processorHandle) { destroyProcessor(g_processorHandle); } g_processorHandle createProcessor(); if (g_processorHandle) { return JsiCreateBoolean(runtime, true); } else { return JsiCreateBoolean(runtime, false); } } // 2. 暴露给JS的函数处理图像 static JsiValueRef NativeProcessImage(JsiRuntimeHandle runtime, JsiValueRef thisVal, int argc, JsiValueRef argv[]) { // 参数检查期望第一个参数是ArrayBuffer输入图像数据第二、三个是宽高number if (argc 3 || !JsiValueIsArrayBuffer(runtime, argv[0]) || !JsiValueIsNumber(runtime, argv[1]) || !JsiValueIsNumber(runtime, argv[2])) { JsiThrowError(runtime, JsiCreateStringUtf8(runtime, Invalid arguments. Expected (ArrayBuffer, width, height))); return JsiCreateUndefined(runtime); } if (!g_processorHandle) { JsiThrowError(runtime, JsiCreateStringUtf8(runtime, Processor not initialized. Call initProcessor first.)); return JsiCreateUndefined(runtime); } // 提取JS ArrayBuffer的信息 uint8_t* inputData nullptr; size_t inputLength 0; JsiGetArrayBufferInfo(runtime, argv[0], (void**)inputData, inputLength); int width static_castint(JsiValueToNumber(runtime, argv[1])); int height static_castint(JsiValueToNumber(runtime, argv[2])); // 验证数据长度是否匹配 (RGBA: width * height * 4) size_t expectedLength width * height * 4; if (inputLength ! expectedLength) { JsiThrowError(runtime, JsiCreateStringUtf8(runtime, ArrayBuffer length does not match width*height*4)); return JsiCreateUndefined(runtime); } // 创建输出ArrayBuffer JsiValueRef outputArrayBuffer JsiCreateArrayBuffer(runtime, expectedLength); uint8_t* outputData nullptr; size_t outputLength 0; JsiGetArrayBufferInfo(runtime, outputArrayBuffer, (void**)outputData, outputLength); // 调用C核心函数 bool success processImage(g_processorHandle, inputData, width, height, outputData); if (success) { return outputArrayBuffer; // 返回新的ArrayBuffer给JS } else { // 如果失败确保不要返回无效的ArrayBuffer可以返回undefined或null // 注意outputArrayBuffer会被自动GC因为我们已经不再持有它的引用除了返回它。 // 如果失败我们不返回它它就会被回收。 JsiThrowError(runtime, JsiCreateStringUtf8(runtime, Image processing failed)); return JsiCreateUndefined(runtime); } } // 3. 暴露给JS的函数释放处理器 static JsiValueRef NativeReleaseProcessor(JsiRuntimeHandle runtime, JsiValueRef thisVal, int argc, JsiValueRef argv[]) { if (g_processorHandle) { destroyProcessor(g_processorHandle); g_processorHandle nullptr; } return JsiCreateUndefined(runtime); } // 4. 模块初始化函数 - 这个函数名是约定的如Init在JS加载.so时被调用 extern C __attribute__((visibility(default))) void Init(JsiRuntimeHandle runtime) { // 创建一个JS对象作为我们模块的命名空间 JsiValueRef moduleObj JsiCreateObject(runtime); // 定义要暴露的函数 JsiPropertyDescriptor funcDesc[3] {{0}}; // initProcessor 函数 funcDesc[0].name JsiCreateStringUtf8(runtime, initProcessor); funcDesc[0].method NativeInitProcessor; funcDesc[0].attributes JSI_PROPERTY_ATTR_DEFAULT; // processImage 函数 funcDesc[1].name JsiCreateStringUtf8(runtime, processImage); funcDesc[1].method NativeProcessImage; funcDesc[1].attributes JSI_PROPERTY_ATTR_DEFAULT; // releaseProcessor 函数 funcDesc[2].name JsiCreateStringUtf8(runtime, releaseProcessor); funcDesc[2].method NativeReleaseProcessor; funcDesc[2].attributes JSI_PROPERTY_ATTR_DEFAULT; // 将这些属性定义到模块对象上 JsiDefineProperties(runtime, moduleObj, funcDesc, 3); // 将模块对象导出到JS全局环境具体导出方式可能因鸿蒙版本略有不同常见的是设置到global的某个属性下 // 假设约定是将对象设置到 globalThis.__imageProcessorModule JsiValueRef global JsiGetGlobalObject(runtime); JsiSetProperty(runtime, global, JsiCreateStringUtf8(runtime, __imageProcessorModule), moduleObj); }第四步编写编译脚本 (CMakeLists.txt)cmake_minimum_required(VERSION 3.12) project(imageProcessor) set(CMAKE_CXX_STANDARD 11) # 假设鸿蒙NDK路径已设置这里需要链接JSVM库 find_library(JSVM_LIB jsvm) # 库名可能为 libjsvm.so 或类似 add_library(imageProcessor SHARED image_processor.cpp jsvm_adapter.cpp ) target_include_directories(imageProcessor PRIVATE ${OHOS_NDK_INCLUDE_DIR} # 鸿蒙NDK头文件路径包含jsvm_api.h ) target_link_libraries(imageProcessor PRIVATE ${JSVM_LIB})4.2 JS/ArkTS侧调用第一步配置项目依赖在你的鸿蒙应用项目的build-profile.json或模块级build.gradle中确保包含对Native模块的构建支持并将编译好的libimageProcessor.so打包到应用的libs/{abi}目录下。第二步编写ArkTS调用代码 (Index.ets)// 导入原生模块。鸿蒙Next的具体导入语法请参考官方文档这里是一种示意。 // 可能需要通过 ohos.napi 或特定的 loadNativeModule 接口。 // 假设我们通过全局对象获取在Init函数中导出的模块 const nativeModule globalThis.__imageProcessorModule; Entry Component struct Index { State message: string Hello, JSVM-API; State processedImageData: ArrayBuffer | null null; // 存储处理后的图像数据 build() { Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() { this.testImageProcessing(); }) // 这里可以添加一个Canvas组件用于显示processedImageData if (this.processedImageData) { Canvas(this.processedImageData) // 假设有这样一个自定义组件 } } .width(100%) } .height(100%) } async testImageProcessing() { try { // 1. 初始化处理器 let initSuccess nativeModule.initProcessor(); if (!initSuccess) { console.error(Failed to init processor); return; } // 2. 准备测试图像数据 (模拟一个 2x2 的RGBA图像) const width 2; const height 2; // 像素数据: [R,G,B,A, R,G,B,A, ...] const inputData new Uint8Array([ 255, 0, 0, 255, // 红 0, 255, 0, 255, // 绿 0, 0, 255, 255, // 蓝 255, 255, 0, 255 // 黄 ]); // 3. 调用原生处理函数 const outputArrayBuffer: ArrayBuffer nativeModule.processImage(inputData.buffer, width, height); // 4. 处理结果 const outputData new Uint8Array(outputArrayBuffer); console.log(Processed pixel (0,0):, outputData[0], outputData[1], outputData[2], outputData[3]); // 应该是灰度值 this.processedImageData outputArrayBuffer; // 触发UI更新 this.message Processing Done!; // 5. 释放资源 (可选或在页面销毁时调用) // nativeModule.releaseProcessor(); } catch (error) { console.error(JSVM-API call failed:, error); this.message Error occurred; } } }5. 常见问题、调试技巧与性能优化5.1 编译与链接问题未定义引用undefined reference确保你的CMakeLists.txt正确链接了libjsvm.so或类似名称的库。鸿蒙NDK中该库的路径和名称需确认。Init函数找不到确保你的适配层cpp文件中Init函数使用了正确的导出修饰符如extern C __attribute__((visibility(default)))并且函数签名与运行时期望的完全一致返回void参数为JsiRuntimeHandle。函数名可能严格区分大小写。ABI不匹配确保你编译的.so文件的ABIarmeabi-v7a, arm64-v8a, x86_64与你的鸿蒙应用目标设备匹配。在应用打包时需要为每个ABI提供对应的.so文件。5.2 运行时崩溃与调试段错误Segmentation Fault最常见原因访问了无效的JsiValueRef或已释放的JsiRef。仔细检查JsiAcquireValue/JsiReleaseValue和JsiCreateReference/JsiDeleteReference是否配对。线程问题在非创建JsiValueRef的线程中使用了它。确保所有JSVM API调用都发生在正确的线程通常是调用原生函数的线程或通过JsiCreateAsyncWork指定的完成回调线程。类型转换错误在调用JsiValueToNumber等函数前没有用JsiValueIsNumber进行检查。内存泄漏使用JsiCreateReference创建的每个引用最终都必须用JsiDeleteReference删除。确保JsiAcquireValue和JsiReleaseValue成对出现即使在发生错误提前返回的情况下。可以考虑在Debug版本中维护一个简单的引用计数日志。调试工具HiLog在C/C代码中广泛使用鸿蒙的HiLog打印日志这是追踪执行流程和变量值最基本有效的方法。DevEco Studio Debugger对于Native代码可以配置C/C调试器进行断点调试。需要将带符号的.so文件与源码关联。ArkTS/JS ConsoleJS侧的异常和console.log输出是定位问题起点的重要依据。5.3 性能优化建议减少跨语言调用次数每次JS到C的调用都有开销。避免在循环中频繁调用简单的原生函数。尽量设计“一次调用处理批量数据”的接口。善用ArrayBuffer处理大数据如图像、音频、文件数据永远使用ArrayBuffer或TypedArray的.buffer进行传递。如上述示例所示这能最大程度减少内存拷贝。在C侧通过JsiGetArrayBufferInfo获取指针直接操作内存。异步化耗时操作任何可能阻塞超过16ms一帧时间的操作都必须放在工作线程中执行并通过异步回调通知JS。使用JsiCreateAsyncWork是官方推荐的方式。避免在C侧创建大量临时JS对象例如在循环中不断JsiCreateStringUtf8来拼接字符串效率极低。应在C侧用std::string或std::stringstream处理好最后一次性创建JS字符串返回。缓存持久化引用对于需要重复使用的JS函数或对象如回调函数在初始化时创建一次持久化引用而不是每次调用都去查找或创建。5.4 类型映射与复杂对象传递对于更复杂的数据结构如JS对象你需要手动解析// C侧解析一个JS对象 { width: 100, height: 200, format: RGBA } JsiValueRef configObj argv[0]; int width 0, height 0; const char* format nullptr; JsiValueRef widthVal JsiGetProperty(runtime, configObj, JsiCreateStringUtf8(runtime, width)); if (JsiValueIsNumber(runtime, widthVal)) { width (int)JsiValueToNumber(runtime, widthVal); } // JsiGetProperty返回的临时值通常不需要Release除非你创建了它的Reference。 JsiValueRef heightVal JsiGetProperty(runtime, configObj, JsiCreateStringUtf8(runtime, height)); if (JsiValueIsNumber(runtime, heightVal)) { height (int)JsiValueToNumber(runtime, heightVal); } JsiValueRef formatVal JsiGetProperty(runtime, configObj, JsiCreateStringUtf8(runtime, format)); if (JsiValueIsString(runtime, formatVal)) { format JsiValueToCString(runtime, formatVal, nullptr); // 注意format指向的字符串内存是临时的如果需要保存必须立即拷贝。 std::string formatStr(format); // 立即拷贝 // ... 使用 formatStr }反之从C返回一个复杂对象给JS需要一步步创建JsiValueRef createResultObject(JsiRuntimeHandle runtime, int width, int height, bool success) { JsiValueRef resultObj JsiCreateObject(runtime); JsiSetProperty(runtime, resultObj, JsiCreateStringUtf8(runtime, width), JsiNumberToValue(runtime, width)); JsiSetProperty(runtime, resultObj, JsiCreateStringUtf8(runtime, height), JsiNumberToValue(runtime, height)); JsiSetProperty(runtime, resultObj, JsiCreateStringUtf8(runtime, success), JsiBooleanToValue(runtime, success)); return resultObj; }跨语言交互的代码总是比单语言更复杂但一旦掌握了JSVM-API这套“外交辞令”和“交通规则”你就能在鸿蒙生态中让JS的灵活与C/C的高效完美协作开发出能力更强、体验更佳的应用。