未验证 提交 1c8e091f 编写于 作者: O openharmony_ci 提交者: Gitee

!16781 update napi-guidelines.md

Merge pull request !16781 from huangzhenghua/master20230331
# 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<string>): void;
function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void;
function get(key: string, defaultValue?: string): Promise<string>;
function set(key: string, value: string, callback: AsyncCallback<string>): void;
function remove(key: string, callback: AsyncCallback<void>): void;
function clear(callback: AsyncCallback<void>): 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 <thread>
### 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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册