• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![cProject](figures/cProject.png)
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