1# 使用Node-API实现跨语言交互开发流程 2<!--Kit: NDK--> 3<!--Subsystem: arkcompiler--> 4<!--Owner: @xliu-huanwei; @shilei123; @huanghello--> 5<!--Designer: @shilei123--> 6<!--Tester: @kirl75; @zsw_zhushiwei--> 7<!--Adviser: @fang-jinxu--> 8 9 10使用Node-API实现跨语言交互,首先需要按照Node-API的机制实现模块的注册和加载等相关动作。 11 12 13- **ArkTS/JS侧**:实现C++方法的调用。代码比较简单,import一个对应的so库后,即可调用C++方法。 14 15- **Native侧**:.cpp文件,实现模块的注册。需要提供注册lib库的名称,并在注册回调方法中定义接口的映射关系,即Native方法及对应的JS/ArkTS接口名称等。 16 17 18此处以在ArkTS/JS侧实现add()接口、在Native侧实现Add()接口,从而实现跨语言交互为例,呈现使用Node-API进行跨语言交互的流程。 19 20 21## 创建Native C++工程 22 23- 在DevEco Studio中**New > Create Project**,选择**Native C++**模板,点击**Next**,选择API版本,设置好工程名称,点击**Finish**,创建得到新工程。 24 25- 创建工程后工程结构可以分两部分,cpp部分和ets部分,工程结构具体介绍可见<!--RP1-->[C++工程目录结构](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-project-structure-V5)<!--RP1End-->。 26 27 28## Native侧方法的实现 29 30- 设置模块注册信息 31 32 ArkTS侧import native模块时,会加载其对应的so。加载so时,首先会调用napi_module_register方法,将模块注册到系统中,并调用模块初始化函数。 33 34 napi_module有两个关键属性:一个是.nm_register_func,定义模块初始化函数;另一个是.nm_modname,定义模块的名称,也就是ArkTS侧引入的so库的名称,模块系统会根据此名称来区分不同的so。 35 36 ``` 37 // entry/src/main/cpp/napi_init.cpp 38 39 // 准备模块加载相关信息,将上述Init函数与本模块名等信息记录下来。 40 static napi_module demoModule = { 41 .nm_version = 1, 42 .nm_flags = 0, 43 .nm_filename = nullptr, 44 .nm_register_func = Init, 45 .nm_modname = "entry", 46 .nm_priv = ((void*)0), 47 .reserved = {0}, 48 }; 49 50 // 加载so时,该函数会自动被调用,将上述demoModule模块注册到系统中。 51 extern "C" __attribute__((constructor)) void RegisterDemoModule() { 52 napi_module_register(&demoModule); 53 } 54 ``` 55注:以上代码无须复制,创建Native C++工程以后在napi_init.cpp代码中已配置好。 56 57- 模块初始化 58 59 实现ArkTS接口与C++接口的绑定和映射。 60 61 ``` 62 // entry/src/main/cpp/napi_init.cpp 63 EXTERN_C_START 64 // 模块初始化 65 static napi_value Init(napi_env env, napi_value exports) { 66 // ArkTS接口与C++接口的绑定和映射 67 napi_property_descriptor desc[] = { 68 // 注:仅需复制以下两行代码,Init在完成创建Native C++工程以后在napi_init.cpp中已配置好。 69 {"callNative", nullptr, CallNative, nullptr, nullptr, nullptr, napi_default, nullptr}, 70 {"nativeCallArkTS", nullptr, NativeCallArkTS, nullptr, nullptr, nullptr, napi_default, nullptr} 71 }; 72 // 在exports对象上挂载CallNative/NativeCallArkTS两个Native方法 73 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 74 return exports; 75 } 76 EXTERN_C_END 77 78 ``` 79 80- 在index.d.ts文件中,提供JS侧的接口方法。 81 82 ``` 83 // entry/src/main/cpp/types/libentry/index.d.ts 84 export const callNative: (a: number, b: number) => number; 85 export const nativeCallArkTS: (cb: (a: number) => number) => number; 86 ``` 87 88- 在oh-package.json5文件中将index.d.ts与cpp文件关联起来。 89 90 ``` 91 // entry/src/main/cpp/types/libentry/oh-package.json5 92 { 93 "name": "libentry.so", 94 "types": "./index.d.ts", 95 "version": "", 96 "description": "Please describe the basic information." 97 } 98 ``` 99 100- 在CMakeLists.txt文件中配置CMake打包参数。 101 102 ``` 103 # entry/src/main/cpp/CMakeLists.txt 104 cmake_minimum_required(VERSION 3.4.1) 105 project(MyApplication2) 106 107 set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 108 109 include_directories(${NATIVERENDER_ROOT_PATH} 110 ${NATIVERENDER_ROOT_PATH}/include) 111 112 # 添加名为entry的库 113 add_library(entry SHARED napi_init.cpp) 114 # 构建此可执行文件需要链接的库 115 target_link_libraries(entry PUBLIC libace_napi.z.so) 116 ``` 117 118- 实现Native侧的CallNative以及NativeCallArkTS接口。具体代码如下: 119 120 ``` 121 // entry/src/main/cpp/napi_init.cpp 122 static napi_value CallNative(napi_env env, napi_callback_info info) 123 { 124 size_t argc = 2; 125 // 声明参数数组 126 napi_value args[2] = {nullptr}; 127 128 // 获取传入的参数并依次放入参数数组中 129 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 130 131 // 依次获取参数 132 double value0; 133 napi_get_value_double(env, args[0], &value0); 134 double value1; 135 napi_get_value_double(env, args[1], &value1); 136 137 // 返回两数相加的结果 138 napi_value sum; 139 napi_create_double(env, value0 + value1, &sum); 140 return sum; 141 } 142 143 static napi_value NativeCallArkTS(napi_env env, napi_callback_info info) 144 { 145 size_t argc = 1; 146 // 声明参数数组 147 napi_value args[1] = {nullptr}; 148 149 // 获取传入的参数并依次放入参数数组中 150 napi_get_cb_info(env, info, &argc, args , nullptr, nullptr); 151 152 // 创建一个int,作为ArkTS的入参 153 napi_value argv = nullptr; 154 napi_create_int32(env, 2, &argv ); 155 156 // 调用传入的callback,并将其结果返回 157 napi_value result = nullptr; 158 napi_call_function(env, nullptr, args[0], 1, &argv, &result); 159 return result; 160 } 161 ``` 162 163 164## ArkTS侧调用C/C++方法实现 165 166ArkTS侧通过import引入Native侧包含处理逻辑的so来使用C/C++的方法。 167 168``` 169// entry/src/main/ets/pages/Index.ets 170// 通过import的方式,引入Native能力。 171import nativeModule from 'libentry.so' 172 173@Entry 174@Component 175struct Index { 176 @State message: string = 'Test Node-API callNative result: '; 177 @State message2: string = 'Test Node-API nativeCallArkTS result: '; 178 build() { 179 Row() { 180 Column() { 181 // 第一个按钮,调用add方法,对应到Native侧的CallNative方法,进行两数相加。 182 Text(this.message) 183 .fontSize(50) 184 .fontWeight(FontWeight.Bold) 185 .onClick(() => { 186 this.message += nativeModule.callNative(2, 3); 187 }) 188 // 第二个按钮,调用nativeCallArkTS方法,对应到Native的NativeCallArkTS,在Native调用ArkTS function。 189 Text(this.message2) 190 .fontSize(50) 191 .fontWeight(FontWeight.Bold) 192 .onClick(() => { 193 this.message2 += nativeModule.nativeCallArkTS((a: number)=> { 194 return a * 2; 195 }); 196 }) 197 } 198 .width('100%') 199 } 200 .height('100%') 201 } 202} 203``` 204 205 206## Node-API的约束限制 207 208 209### SO命名规则 210 211导入使用的模块名和注册时的模块名大小写保持一致,如模块名为entry,则so的名字为libentry.so,napi_module中nm_modname字段应为entry,ArkTS侧使用时写作:import xxx from 'libentry.so'。 212 213 214### 注册建议 215 216- nm_register_func对应的函数(如上述Init函数)需要加上static,防止与其他so里的符号冲突。 217 218- 模块注册的入口,即使用__attribute__((constructor))修饰的函数的函数名(如上述RegisterDemoModule函数)需要确保不与其它模块重复。 219 220 221### 多线程限制 222 223每个引擎实例对应一个JS线程,实例上的对象不能跨线程操作,否则会引起应用crash。使用时需要遵循如下原则: 224 225- Node-API接口只能在JS线程使用。 226- Native接口入参env与特定JS线程绑定只能在创建时的线程使用。 227- 使用Node-API接口创建的数据需在env完全销毁前进行释放,避免内存泄漏。此外,在napi_env销毁后访问/使用这些数据,可能会导致进程崩溃。 228 229部分常见错误用法已增加维测手段覆盖,详见[使用Node-API接口产生的异常日志/崩溃分析](use-napi-about-crash.md)。 230