1# N-API在应用工程中的使用指导 2 3在OpenHarmony中,C API中的N-API接口可以实现ArkTS/TS/JS与C/C++之间的交互。N-API提供的接口名与三方Node.js一致,目前支持部分接口,支持列表见[链接](https://gitee.com/openharmony/arkui_napi/blob/master/libnapi.ndk.json)。 4 5## 开发流程 6 7在DevEco Studio的模板工程中包含使用N-API的默认工程,使用`File`->`New`->`Create Project`创建`Native C++`模板工程。创建后在`entry/src/main`目录下会包含`cpp`目录,可以使用N-API接口,开发C/C++代码(native侧代码)。 8 9ArkTS/TS/JS侧通过`import`引入native侧的so文件,如:`import hello from 'libhello.so'`,意为使用libhello.so的能力,并将名为`hello`的ArkTS/TS/JS对象给到应用的ArkTS/TS/JS侧,开发者可通过该对象,调用到在`cpp`中开发的native方法。 10 11## 基本功能 12N-API接口可以实现ArkTS/TS/JS和C/C++之间的交互,这里以HelloWorld工程的两个例子: 131. 提供一个名为`Add`的native方法,ArkTS侧调用该方法并传入两个number,native方法将这两个number相加并返回到ArkTS侧。 142. 提供一个名为`NativeCallArkTS`的native方法,ArkTS侧调用该方法并传入一个ArkTS function,native方法中调用这个ArkTS function,并将其结果返回ArkTS侧。 15 16以此来介绍: 171. ArkTS侧如何调用到C++侧方法。 182. C++侧如何调用到ArkTS侧方法。 19 20下面给出了工程中的: 211. `entry\src\main\cpp\hello.cpp`, 包含native侧逻辑。 222. `entry\src\main\ets\pages\index.ets`,包含ArkTS侧逻辑。 233. `entry\src\main\cpp\types\libentry\index.d.ts`,包含native侧暴露给ArkTS侧接口的声明。 24 25同时给出了注解,工程中其余部分均与native默认工程相同。 26 27```C++ 28// entry\src\main\cpp\hello.cpp 29// 引入N-API相关头文件。 30#include "napi/native_api.h" 31 32// 开发者提供的native方法,入参有且仅有如下两个,开发者不需进行变更。 33// napi_env 为当前运行的上下文。 34// napi_callback_info 记录了一些信息,包括从ArkTS侧传递过来参数等。 35static napi_value Add(napi_env env, napi_callback_info info) 36{ 37 // 期望从ArkTS侧获取的参数的数量,napi_value可理解为ArkTS value在native方法中的表现形式。 38 size_t argc = 2; 39 napi_value args[2] = {nullptr}; 40 41 // 从info中,拿到从ArkTS侧传递过来的参数,此处获取了两个ArkTS参数,即arg[0]和arg[1]。 42 napi_get_cb_info(env, info, &argc, args , nullptr, nullptr); 43 44 // 将获取的ArkTS参数转换为native信息,此处ArkTS侧传入了两个number,这里将其转换为native侧可以操作的double类型。 45 double value0; 46 napi_get_value_double(env, args[0], &value0); 47 48 double value1; 49 napi_get_value_double(env, args[1], &value1); 50 51 // native侧的业务逻辑,这里简单以两数相加为例。 52 double nativeSum = value0 + value1; 53 54 // 此处将native侧业务逻辑处理结果转换为ArkTS值,并返回给ArkTS。 55 napi_value sum; 56 napi_create_double(env, nativeSum , &sum); 57 return sum; 58} 59 60static napi_value NativeCallArkTS(napi_env env, napi_callback_info info) 61{ 62 // 期望从ArkTS侧获取的参数的数量,napi_value可理解为ArkTS value在native方法中的表现形式。 63 size_t argc = 1; 64 napi_value args[1] = {nullptr}; 65 66 // 从info中,拿到从ArkTS侧传递过来的参数,此处获取了一个ArkTS参数,即arg[0]。 67 napi_get_cb_info(env, info, &argc, args , nullptr, nullptr); 68 69 // 创建一个ArkTS number作为ArkTS function的入参。 70 napi_value argv = nullptr; 71 napi_create_int32(env, 10, &argv); 72 73 napi_value result = nullptr; 74 // native方法中调用ArkTS function,其返回值保存到result中并返到ArkTS侧。 75 napi_call_function(env, nullptr, args[0], 1, &argv, &result); 76 77 return result; 78} 79 80EXTERN_C_START 81// Init将在exports上挂上Add/NativeCallArkTS这些native方法,此处的exports就是开发者import之后获取到的ArkTS对象。 82static napi_value Init(napi_env env, napi_value exports) 83{ 84 // 函数描述结构体,以Add为例,第三个参数"Add"为上述的native方法, 85 // 第一个参数"add"为ArkTS侧对应方法的名称。 86 napi_property_descriptor desc[] = { 87 { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }, 88 { "nativeCallArkTS", nullptr, NativeCallArkTS, nullptr, nullptr, nullptr, napi_default, nullptr }, 89 }; 90 // 在exports这个ArkTS对象上,挂载native方法。 91 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 92 return exports; 93} 94EXTERN_C_END 95 96// 准备模块加载相关信息,将上述Init函数与本模块名等信息记录下来。 97static napi_module demoModule = { 98 .nm_version =1, 99 .nm_flags = 0, 100 .nm_filename = nullptr, 101 .nm_register_func = Init, 102 .nm_modname = "entry", 103 .nm_priv = ((void*)0), 104 .reserved = { 0 }, 105}; 106 107// 打开so时,该函数将自动被调用,使用上述demoModule模块信息,进行模块注册相关动作。 108extern "C" __attribute__((constructor)) void RegisterHelloModule(void) 109{ 110 napi_module_register(&demoModule); 111} 112``` 113 114```js 115// entry\src\main\ets\pages\index.ets 116 117import hilog from '@ohos.hilog'; 118// 通过import的方式,引入native能力。 119import entry from 'libentry.so' 120 121@Entry 122@Component 123struct Index { 124 125 build() { 126 Row() { 127 Column() { 128 // 第一个按钮,调用add方法,对应到native侧的Add方法,进行两数相加。 129 Button('ArkTS call C++') 130 .fontSize(50) 131 .fontWeight(FontWeight.Bold) 132 .onClick(() => { 133 hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO); 134 hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', entry.add(2, 3)); 135 }) 136 // 第二个按钮,调用nativeCallArkTS方法,对应到native的NativeCallArkTS,在native中执行ArkTS function。 137 Button('C++ call ArkTS') 138 .fontSize(50) 139 .fontWeight(FontWeight.Bold) 140 .onClick(() => { 141 hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO); 142 let ret = entry.nativeCallArkTS((value)=>{return value * 2;}); 143 hilog.info(0x0000, 'testTag', 'Test NAPI nativeCallArkTS ret = %{public}d', ret); 144 }) 145 } 146 .width('100%') 147 } 148 .height('100%') 149 } 150} 151 152``` 153 154```js 155// entry\src\main\cpp\types\libentry\index.d.ts 156// native侧暴露给ArkTS侧接口的声明。 157export const add: (a: number, b: number) => number; 158export const nativeCallArkTS: (a: object) => number; 159``` 160 161## 开发建议 162 163### 注册建议 164 165* nm_register_func对应的函数(如上述Init函数)需要加上static,防止与其他so里的符号冲突。 166* 模块注册的入口,即使用\_\_attribute\_\_((constructor))修饰的函数的函数名(如上述RegisterHelloModule函数)需要确保不与其他模块重复。 167 168### so命名规则 169 170**so命名必须符合以下规则:** 171 172* 每个模块对应一个so。 173* 如模块名为`hello`,则so的名字为`libhello.so`,`napi_module`中`nm_modname`字段应为`hello`,大小写与模块名保持一致,应用使用时写作:`import hello from 'libhello.so'`。 174 175### JS对象线程限制 176 177ArkCompiler会对JS对象线程进行保护,使用不当会引起应用crash,因此需要遵循如下原则: 178 179* N-API接口只能在JS线程使用。 180* env与线程绑定,不能跨线程使用。native侧JS对象只能在创建时的线程使用,即与线程所持有的env绑定。 181 182### 头文件引入限制 183 184在引入头文件时,需引入"napi/native_api.h",否则会出现N-API接口无法找到的编译报错。 185 186 187## 相关实例 188 189针对N-API的开发,有以下相关完整实例可供参考: 190 191- [第一个Native C++应用(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/NativeTemplateDemo) 192 193- [Native Component(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/XComponent)