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