diff --git a/zh-cn/application-dev/napi/napi-guidelines.md b/zh-cn/application-dev/napi/napi-guidelines.md index 39a1a1d09fc3f4d02c3eb3a02e8274798b7acee4..e2788d6106d55f287f045f51a68841bc44e18b7d 100644 --- a/zh-cn/application-dev/napi/napi-guidelines.md +++ b/zh-cn/application-dev/napi/napi-guidelines.md @@ -1,652 +1,192 @@ -# Native API在应用工程中的使用指导 +# N-API在应用工程中的使用指导 -OpenHarmony的应用必须用js来桥接native。需要使用[ace_napi](https://gitee.com/openharmony/arkui_napi/tree/master)仓中提供的napi接口来处理js交互。napi提供的接口名与三方Node.js一致,目前支持部分接口,符号表见ace_napi仓中的`libnapi.ndk.json`文件。 +在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)。 ## 开发流程 -在DevEco Studio的模板工程中包含使用Native API的默认工程,使用`File`->`New`->`Create Project`创建`Native C++`模板工程。创建后在`main`目录下会包含`cpp`目录,可以使用ace_napi仓下提供的napi接口进行开发。 +在DevEco Studio的模板工程中包含使用N-API的默认工程,使用`File`->`New`->`Create Project`创建`Native C++`模板工程。创建后在`entry/src/main`目录下会包含`cpp`目录,可以使用N-API接口,开发C/C++代码(native侧代码)。 -js侧通过`import`引入native侧包含处理js逻辑的so,如:`import hello from 'libhello.so'`,意为使用libhello.so的能力,native侧通过napi接口创建的js对象会给到应用js侧的`hello`对象。 - -## 开发建议 - -### 注册建议 - -* nm_register_func对应的函数需要加上static,防止与其他so里的符号冲突。 -* 模块注册的入口,即使用\_\_attribute\_\_((constructor))修饰的函数的函数名需要确保不与其他模块重复。 - -### so命名规则 - -**so命名必须符合以下规则:** - -* 每个模块对应一个so。 -* 如模块名为`hello`,则so的名字为`libhello.so`,`napi_module`中`nm_modname`字段应为`hello`,大小写与模块名保持一致,应用使用时写作:`import hello from 'libhello.so'`。 - -### js对象线程限制 - -ark引擎会对js对象线程使用进行保护,使用不当会引起应用crash,因此需要遵循如下原则: - -* napi接口只能在js线程使用。 -* env与线程绑定,不能跨线程使用。native侧js对象只能在创建时的线程使用,即与线程所持有的env绑定。 - -### 头文件引入限制 - -在使用napi的对象和方法时需要引用"napi/native_api.h"。否则在只引入三方库头文件时,会出现接口无法找到的编译报错。 - -### napi_create_async_work接口说明 - -napi_create_async_work里有两个回调: - -* execute:用于异步处理业务逻辑。因为不在js线程中,所以不允许调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。 - -* complete:可以调用napi的接口,将execute中的返回值封装成js对象返回。此回调在js线程中执行。 - -```c++ -napi_status napi_create_async_work(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete, - void* data, - napi_async_work* result) -``` +ArkTS/TS/JS侧通过`import`引入native侧的so文件,如:`import hello from 'libhello.so'`,意为使用libhello.so的能力,并将名为`hello`的ArkTS/TS/JS对象给到应用的ArkTS/TS/JS侧,开发者可通过该对象,调用到在`cpp`中开发的native方法。 +## 基本功能 +N-API接口可以实现ArkTS/TS/JS和C/C++之间的交互,这里以HelloWorld工程的两个例子: +1. 提供一个名为`Add`的native方法,ArkTS侧调用该方法并传入两个number,native方法将这两个number相加并返回到ArkTS侧。 +2. 提供一个名为`NativeCallArkTS`的native方法,ArkTS侧调用该方法并传入一个ArkTS function,native方法中调用这个ArkTS function,并将其结果返回ArkTS侧。 +以此来介绍: +1. ArkTS侧如何调用到C++侧方法。 +2. C++侧如何调用到ArkTS侧方法。 -## storage 模块——同步异步接口封装 +下面给出了工程中的: +1. `entry\src\main\cpp\hello.cpp`, 包含native侧逻辑。 +2. `entry\src\main\ets\pages\index.ets`,包含ArkTS侧逻辑。 +3. `entry\src\main\cpp\types\libentry\index.d.ts`,包含native侧暴露给ArkTS侧接口的声明。 -### 模块简介 +同时给出了注解,工程中其余部分均与native默认工程相同。 -本示例通过实现 `storage` 模块展示了同步和异步方法的封装。`storage ` 模块实现了数据的保存、获取、删除、清除功能。 - -### 接口声明 - -```typescript -import { AsyncCallback } from './basic'; -declare namespace storage { - function get(key: string, callback: AsyncCallback): void; - function get(key: string, defaultValue: string, callback: AsyncCallback): void; - function get(key: string, defaultValue?: string): Promise; - function set(key: string, value: string, callback: AsyncCallback): void; - function remove(key: string, callback: AsyncCallback): void; - function clear(callback: AsyncCallback): void; - function getSync(key: string, defaultValue?: string): string; - function setSync(key: string, value: string): void; - function removeSync(key: string): void; - function clearClear(): void; -} -export default storage; -``` - - - -### 具体实现 - -完整代码参见仓下路径:[OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master)仓库`sample/native_module_storage/` - -**1、模块注册** - -如下,注册了4个同步接口(`getSync`、`setSync`、`removeSync`、`clearSync`)、4个异步接口(`get`、`set`、`remove`、`clear`)。 - -```c++ -/*********************************************** - * Module export and register - ***********************************************/ -static napi_value StorageExport(napi_env env, napi_value exports) -{ - napi_property_descriptor desc[] = { - DECLARE_NAPI_FUNCTION("get", JSStorageGet), - DECLARE_NAPI_FUNCTION("set", JSStorageSet), - DECLARE_NAPI_FUNCTION("remove", JSStorageDelete), - DECLARE_NAPI_FUNCTION("clear", JSStorageClear), - - DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync), - DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync), - DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync), - DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync), - }; - NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); - return exports; -} +```C++ +// entry\src\main\cpp\hello.cpp +// 引入N-API相关头文件。 +#include "napi/native_api.h" -// storage module -static napi_module storage_module = {.nm_version = 1, - .nm_flags = 0, - .nm_filename = nullptr, - .nm_register_func = StorageExport, - .nm_modname = "storage", - .nm_priv = ((void*)0), - .reserved = {0}}; - -// storage module register -extern "C" __attribute__((constructor)) void StorageRegister() +// 开发者提供的native方法,入参有且仅有如下两个,开发者不需进行变更。 +// napi_env 为当前运行的上下文。 +// napi_callback_info 记录了一些信息,包括从ArkTS侧传递过来参数等。 +static napi_value Add(napi_env env, napi_callback_info info) { - napi_module_register(&storage_module); -} -``` - -**2、getSync 函数实现** + // 期望从ArkTS侧获取的参数的数量,napi_value可理解为ArkTS value在native方法中的表现形式。 + size_t argc = 2; + napi_value args[2] = {nullptr}; + + // 从info中,拿到从ArkTS侧传递过来的参数,此处获取了两个ArkTS参数,即arg[0]和arg[1]。 + napi_get_cb_info(env, info, &argc, args , nullptr, nullptr); -如上注册时所写,`getSync` 对应的函数是 `JSStorageGetSync` 。从 `gKeyValueStorage` 中获取数据后,创建一个字符串对象并返回。 + // 将获取的ArkTS参数转换为native信息,此处ArkTS侧传入了两个number,这里将其转换为native侧可以操作的double类型。 + double value0; + napi_get_value_double(env, args[0], &value0); -```c -static napi_value JSStorageGetSync(napi_env env, napi_callback_info info) -{ - GET_PARAMS(env, info, 2); - NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); - char key[32] = {0}; - size_t keyLen = 0; - char value[128] = {0}; - size_t valueLen = 0; - - // 参数解析 - for (size_t i = 0; i < argc; i++) { - napi_valuetype valueType; - napi_typeof(env, argv[i], &valueType); - - if (i == 0 && valueType == napi_string) { - napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen); - } else if (i == 1 && valueType == napi_string) { - napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen); - break; - } else { - NAPI_ASSERT(env, false, "type mismatch"); - } - } - - // 获取数据的业务逻辑,这里简单地从一个全局变量中获取 - auto itr = gKeyValueStorage.find(key); - napi_value result = nullptr; - if (itr != gKeyValueStorage.end()) { - // 获取到数据后创建一个string类型的JS对象 - napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result); - } else if (valueLen > 0) { - // 没有获取到数据使用默认值创建JS对象 - napi_create_string_utf8(env, value, valueLen, &result); - } else { - NAPI_ASSERT(env, false, "key does not exist"); - } - // 返回结果 - return result; + double value1; + napi_get_value_double(env, args[1], &value1); + + // native侧的业务逻辑,这里简单以两数相加为例。 + double nativeSum = value0 + value1; + + // 此处将native侧业务逻辑处理结果转换为ArkTS值,并返回给ArkTS。 + napi_value sum; + napi_create_double(env, nativeSum , &sum); + return sum; } -``` - -**3、get 函数实现** - -如上注册时所写,`get`对应的函数式`JSStorageGet`。 -```c -static napi_value JSStorageGet(napi_env env, napi_callback_info info) +static napi_value NativeCallArkTS(napi_env env, napi_callback_info info) { - GET_PARAMS(env, info, 3); - NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); - - // StorageAsyncContext是自己定义的一个类,用于保存执行过程中的数据 - StorageAsyncContext* asyncContext = new StorageAsyncContext(); - - asyncContext->env = env; - - // 获取参数 - for (size_t i = 0; i < argc; i++) { - napi_valuetype valueType; - napi_typeof(env, argv[i], &valueType); - - if (i == 0 && valueType == napi_string) { - napi_get_value_string_utf8(env, argv[i], asyncContext->key, 31, &asyncContext->keyLen); - } else if (i == 1 && valueType == napi_string) { - napi_get_value_string_utf8(env, argv[i], asyncContext->value, 127, &asyncContext->valueLen); - } else if (i == 1 && valueType == napi_function) { - napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef); - break; - } else if (i == 2 && valueType == napi_function) { - napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef); - } else { - NAPI_ASSERT(env, false, "type mismatch"); - } - } - + // 期望从ArkTS侧获取的参数的数量,napi_value可理解为ArkTS value在native方法中的表现形式。 + size_t argc = 1; + napi_value args[1] = {nullptr}; + + // 从info中,拿到从ArkTS侧传递过来的参数,此处获取了一个ArkTS参数,即arg[0]。 + napi_get_cb_info(env, info, &argc, args , nullptr, nullptr); + + // 创建一个ArkTS number作为ArkTS function的入参。 + napi_value argv = nullptr; + napi_create_int32(env, 10, &argv); + napi_value result = nullptr; - - // 根据参数判断开发者使用的是promise还是callback - if (asyncContext->callbackRef == nullptr) { - // 创建promise - napi_create_promise(env, &asyncContext->deferred, &result); - } else { - napi_get_undefined(env, &result); - } - - napi_value resource = nullptr; - napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource); - - napi_create_async_work( - env, nullptr, resource, - // 回调1:此回调由napi异步执行,里面就是需要异步执行的业务逻辑。由于是异步线程执行,所以不要在此通过napi接口操作JS对象。 - [](napi_env env, void* data) { - StorageAsyncContext* asyncContext = (StorageAsyncContext*)data; - auto itr = gKeyValueStorage.find(asyncContext->key); - if (itr != gKeyValueStorage.end()) { - strncpy_s(asyncContext->value, 127, itr->second.c_str(), itr->second.length()); - asyncContext->status = 0; - } else { - asyncContext->status = 1; - } - }, - // 回调2:此回调在上述异步回调执行完后执行,此时回到了JS线程来回调开发者传入的回调 - [](napi_env env, napi_status status, void* data) { - StorageAsyncContext* asyncContext = (StorageAsyncContext*)data; - napi_value result[2] = {0}; - if (!asyncContext->status) { - napi_get_undefined(env, &result[0]); - napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]); - } else { - napi_value message = nullptr; - napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message); - napi_create_error(env, nullptr, message, &result[0]); - napi_get_undefined(env, &result[1]); - } - if (asyncContext->deferred) { - // 如果走的是promise,那么判断回调1的结果 - if (!asyncContext->status) { - // 回调1执行成功(status为1)时触发,也就是触发promise里then里面的回调 - napi_resolve_deferred(env, asyncContext->deferred, result[1]); - } else { - // 回调1执行失败(status为0)时触发,也就是触发promise里catch里面的回调 - napi_reject_deferred(env, asyncContext->deferred, result[0]); - } - } else { - // 如果走的是callback,则通过napi_call_function调用callback回调返回结果 - napi_value callback = nullptr; - napi_value returnVal; - napi_get_reference_value(env, asyncContext->callbackRef, &callback); - napi_call_function(env, nullptr, callback, 2, result, &returnVal); - napi_delete_reference(env, asyncContext->callbackRef); - } - napi_delete_async_work(env, asyncContext->work); - delete asyncContext; - }, - (void*)asyncContext, &asyncContext->work); - napi_queue_async_work(env, asyncContext->work); - + // native方法中调用ArkTS function,其返回值保存到result中并返到ArkTS侧。 + napi_call_function(env, nullptr, args[0], 1, &argv, &result); + return result; } -``` -**4、js示例代码** - -```js -import storage from 'libstorage.so'; - -export default { - testGetSync() { - const name = storage.getSync('name'); - console.log('name is ' + name); - }, - testGet() { - storage.get('name') - .then(date => { - console.log('name is ' + data); - }) - .catch(error => { - console.log('error: ' + error); - }); - } -} -``` - - - -## NetServer 模块——native与js对象绑定 - -### 模块简介 - -本示例展示了`on/off/once`订阅方法的实现,同时也包含了 C++ 与 js对象通过 wrap 接口的绑定。NetServer 模块实现了一个网络服务。 - -### 接口声明 - -```typescript -export class NetServer { - function start(port: number): void; - function stop(): void; - function on('start' | 'stop', callback: Function): void; - function once('start' | 'stop', callback: Function): void; - function off('start' | 'stop', callback: Function): void; -} -``` - -### 具体实现 - -完整代码参见:[OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master)仓库`sample/native_module_netserver/` - -**1、模块注册** - -```c -static napi_value NetServer::Export(napi_env env, napi_value exports) +EXTERN_C_START +// Init将在exports上挂上Add/NativeCallArkTS这些native方法,此处的exports就是开发者import之后获取到的ArkTS对象。 +static napi_value Init(napi_env env, napi_value exports) { - const char className[] = "NetServer"; - napi_property_descriptor properties[] = { - DECLARE_NAPI_FUNCTION("start", JS_Start), - DECLARE_NAPI_FUNCTION("stop", JS_Stop), - DECLARE_NAPI_FUNCTION("on", JS_On), - DECLARE_NAPI_FUNCTION("once", JS_Once), - DECLARE_NAPI_FUNCTION("off", JS_Off), + // 函数描述结构体,以Add为例,第三个参数"Add"为上述的native方法, + // 第一个参数"add"为ArkTS侧对应方法的名称。 + napi_property_descriptor desc[] = { + { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "nativeCallArkTS", nullptr, NativeCallArkTS, nullptr, nullptr, nullptr, napi_default, nullptr }, }; - napi_value netServerClass = nullptr; - - napi_define_class(env, className, sizeof(className), JS_Constructor, nullptr, countof(properties), properties, - &netServerClass); - - napi_set_named_property(env, exports, "NetServer", netServerClass); - + // 在exports这个ArkTS对象上,挂载native方法。 + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); return exports; } -``` - -**2、在构造函数中绑定 C++ 与 JS 对象** +EXTERN_C_END -```c -napi_value NetServer::JS_Constructor(napi_env env, napi_callback_info cbinfo) -{ - napi_value thisVar = nullptr; - void* data = nullptr; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, &data); - - // C++ Native对象,准备与JS对象映射在一起 - NetServer* netServer = new NetServer(env, thisVar); - - // 使用napi_wrap将netServer与thisVar(即当前创建的这个JS对象)做绑定 - napi_wrap( - env, thisVar, netServer, - // JS对象由引擎自动回收释放,当JS对象释放时触发该回调,在改回调中释放netServer - [](napi_env env, void* data, void* hint) { - printf("NetServer::Destructor\n"); - NetServer* netServer = (NetServer*)data; - delete netServer; - }, - nullptr, nullptr); - - return thisVar; -} -``` - -**3、从 JS 对象中取出 C++ 对象** +// 准备模块加载相关信息,将上述Init函数与本模块名等信息记录下来。 +static napi_module demoModule = { + .nm_version =1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "entry", + .nm_priv = ((void*)0), + .reserved = { 0 }, +}; -```c -napi_value NetServer::JS_Start(napi_env env, napi_callback_info cbinfo) +// 打开so时,该函数将自动被调用,使用上述demoModule模块信息,进行模块注册相关动作。 +extern "C" __attribute__((constructor)) void RegisterHelloModule(void) { - size_t argc = 1; - napi_value argv[1] = {0}; - napi_value thisVar = nullptr; - void* data = nullptr; - napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data); - - NetServer* netServer = nullptr; - // 通过napi_unwrap从thisVar中取出C++对象 - napi_unwrap(env, thisVar, (void**)&netServer); - - NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); - - napi_valuetype valueType; - napi_typeof(env, argv[0], &valueType); - NAPI_ASSERT(env, valueType == napi_number, "type mismatch for parameter 1"); - - int32_t port = 0; - napi_get_value_int32(env, argv[0], &port); - - // 开启服务 - netServer->Start(port); - - napi_value result = nullptr; - napi_get_undefined(env, &result); - return result; + napi_module_register(&demoModule); } ``` -`netServer->Start`后回调通过`on`注册的`start`事件。 - -```c -int NetServer::Start(int port) -{ - printf("NetServer::Start thread_id: %ld \n", uv_thread_self()); - - struct sockaddr_in addr; - int r; - - uv_ip4_addr("0.0.0.0", port, &addr); - - r = uv_tcp_init(loop_, &tcpServer_); - if (r) { - fprintf(stderr, "Socket creation error\n"); - return 1; - } - - r = uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0); - if (r) { - fprintf(stderr, "Bind error\n"); - return 1; - } - - r = uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection); - if (r) { - fprintf(stderr, "Listen error %s\n", uv_err_name(r)); - return 1; +```js +// entry\src\main\ets\pages\index.ets + +import hilog from '@ohos.hilog'; +// 通过import的方式,引入native能力。 +import entry from 'libentry.so' + +@Entry +@Component +struct Index { + + build() { + Row() { + Column() { + // 第一个按钮,调用add方法,对应到native侧的Add方法,进行两数相加。 + Button('ArkTS call C++') + .fontSize(50) + .fontWeight(FontWeight.Bold) + .onClick(() => { + hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO); + hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', entry.add(2, 3)); + }) + // 第二个按钮,调用nativeCallArkTS方法,对应到native的NativeCallArkTS,在native中执行ArkTS function。 + Button('C++ call ArkTS') + .fontSize(50) + .fontWeight(FontWeight.Bold) + .onClick(() => { + hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO); + let ret = entry.nativeCallArkTS((value)=>{return value * 2;}); + hilog.info(0x0000, 'testTag', 'Test NAPI nativeCallArkTS ret = %{public}d', ret); + }) + } + .width('100%') } - - // 服务启动后触发“start”事件 - Emit("start", nullptr); - - return 0; -} -``` - -**4、注册或释放(on/off/once)事件,以 on 为例** - -```c -napi_value NetServer::JS_On(napi_env env, napi_callback_info cbinfo) -{ - size_t argc = 2; - napi_value argv[2] = {0}; - napi_value thisVar = 0; - void* data = nullptr; - napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data); - - NetServer* netServer = nullptr; - // 通过napi_unwrap取出NetServer指针 - napi_unwrap(env, thisVar, (void**)&netServer); - - NAPI_ASSERT(env, argc >= 2, "requires 2 parameter"); - - // 参数类型校验 - napi_valuetype eventValueType; - napi_typeof(env, argv[0], &eventValueType); - NAPI_ASSERT(env, eventValueType == napi_string, "type mismatch for parameter 1"); - - napi_valuetype eventHandleType; - napi_typeof(env, argv[1], &eventHandleType); - NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 2"); - - char type[64] = {0}; - size_t typeLen = 0; - - napi_get_value_string_utf8(env, argv[0], type, 63, &typeLen); - - // 注册事件handler - netServer->On((const char*)type, argv[1]); - - napi_value result = nullptr; - napi_get_undefined(env, &result); - return result; -} -``` - -**5、js示例代码** - -```javascript -import { NetServer } from 'libnetserver.so'; - -export default { - testNetServer() { - var netServer = new NetServer(); - netServer.on('start', (event) => {}); - netServer.start(1000); // 端口号1000, start完成后回调上面注册的 “start” 回调 + .height('100%') } } -``` - - - -## 在非JS线程中回调JS接口 -### 模块简介 - -本示例介绍如何在非JS线程中回调JS应用的回调函数。例如JS应用中注册了某个sensor的监听,这个sensor的数据是由一个SA服务来上报的,当SA通过IPC调到客户端时,此时的执行线程是一个IPC通信线程,与应用的JS线程是两个不同的线程。这时就需要将执行JS回调的任务抛到JS线程中才能执行,否则会出现崩溃。 - -### 具体实现 - -完整代码参见:[OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master)仓库`sample/native_module_callback/` - -**1、模块注册** - -如下,注册了1个接口`test`,会传入一个参数,类型为包含一个参数的函数。 +``` -```c++ -/*********************************************** - * Module export and register - ***********************************************/ -static napi_value CallbackExport(napi_env env, napi_value exports) -{ - static napi_property_descriptor desc[] = { - DECLARE_NAPI_FUNCTION("test", JSTest) - }; - NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); - return exports; -} +```js +// entry\src\main\cpp\types\libentry\index.d.ts +// native侧暴露给ArkTS侧接口的声明。 +export const add: (a: number, b: number) => number; +export const nativeCallArkTS: (a: object) => number; +``` -// callback module define -static napi_module callbackModule = { - .nm_version = 1, - .nm_flags = 0, - .nm_filename = nullptr, - .nm_register_func = CallbackExport, - .nm_modname = "callback", - .nm_priv = ((void*)0), - .reserved = { 0 }, -}; +## 开发建议 -// callback module register -extern "C" __attribute__((constructor)) void CallbackTestRegister() -{ - napi_module_register(&callbackModule); -} -``` +### 注册建议 -**2、获取env中的loop,抛任务回JS线程** +* nm_register_func对应的函数(如上述Init函数)需要加上static,防止与其他so里的符号冲突。 +* 模块注册的入口,即使用\_\_attribute\_\_((constructor))修饰的函数的函数名(如上述RegisterHelloModule函数)需要确保不与其他模块重复。 -```c++ -#include +### so命名规则 -#include "napi/native_api.h" -#include "napi/native_node_api.h" +**so命名必须符合以下规则:** -#include "uv.h" +* 每个模块对应一个so。 +* 如模块名为`hello`,则so的名字为`libhello.so`,`napi_module`中`nm_modname`字段应为`hello`,大小写与模块名保持一致,应用使用时写作:`import hello from 'libhello.so'`。 -struct CallbackContext { - napi_env env = nullptr; - napi_ref callbackRef = nullptr; - int retData = 0; -}; +### JS对象线程限制 -void callbackTest(CallbackContext* context) -{ - uv_loop_s* loop = nullptr; - // 此处的env需要在注册JS回调时保存下来。从env中获取对应的JS线程的loop。 - napi_get_uv_event_loop(context->env, &loop); - - // 创建uv_work_t用于传递私有数据,注意回调完成后需要释放内存,此处省略生成回传数据的逻辑,传回int类型1。 - uv_work_t* work = new uv_work_t; - context->retData = 1; - work->data = (void*)context; - - // 调用libuv接口抛JS任务到loop中执行。 - uv_queue_work( - loop, - work, - // 此回调在另一个普通线程中执行,用于处理异步任务,回调执行完后执行下面的回调。本场景下该回调不需要执行任务。 - [](uv_work_t* work) {}, - // 此回调会在env对应的JS线程中执行。 - [](uv_work_t* work, int status) { - CallbackContext* context = (CallbackContext*)work->data; - napi_handle_scope scope = nullptr; - // 打开handle scope用于管理napi_value的生命周期,否则会内存泄露。 - napi_open_handle_scope(context->env, &scope); - if (scope == nullptr) { - return; - } - - // 调用napi。 - napi_value callback = nullptr; - napi_get_reference_value(context->env, context->callbackRef, &callback); - napi_value retArg; - napi_create_int32(context->env, context->retData, &retArg); - napi_value ret; - napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret); - napi_delete_reference(context->env, context->callbackRef); - - // 关闭handle scope释放napi_value。 - napi_close_handle_scope(context->env, scope); - - // 释放work指针。 - if (work != nullptr) { - delete work; - } - - delete context; - } - ); -} +ArkCompiler会对JS对象线程进行保护,使用不当会引起应用crash,因此需要遵循如下原则: -static napi_value JSTest(napi_env env, napi_callback_info info) -{ - size_t argc = 1; - napi_value argv[1] = { 0 }; - napi_value thisVar = nullptr; - void* data = nullptr; - napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); - - // 获取第一个入参,即需要后续触发的回调函数 - napi_valuetype valueType = napi_undefined; - napi_typeof(env, argv[0], &valueType); - if (valueType != napi_function) { - return nullptr; - } - // 存下env与回调函数,用于传递 - auto asyncContext = new CallbackContext(); - asyncContext->env = env; - napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef); - // 模拟抛到非js线程执行逻辑 - std::thread testThread(callbackTest, asyncContext); - testThread.detach(); - - return nullptr; -} -``` +* N-API接口只能在JS线程使用。 +* env与线程绑定,不能跨线程使用。native侧JS对象只能在创建时的线程使用,即与线程所持有的env绑定。 -**3、js示例代码** +### 头文件引入限制 -```js -import callback from 'libcallback.so'; +在引入头文件时,需引入"napi/native_api.h",否则会出现N-API接口无法找到的编译报错。 -export default { - testcallback() { - callback.test((data) => { - console.error('test result = ' + data) - }) - } -} -``` ## 相关实例 -针对Native API的开发,有以下相关完整实例可供参考: +针对N-API的开发,有以下相关完整实例可供参考: - [第一个Native C++应用(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/NativeTemplateDemo)