欢迎光临
我们一直在努力

鸿蒙 NDK开发:Node-API开发规范(六)

   在NDK开发中,Node-API是实现ArkTS/JS与C/C++交互的核心机制。遵循Node-API开发规范,可以写出更稳定、更高效、更安全的Native代码。

一、获取JS传入参数及其数量

当传入napi_get_cb_info的argv不为nullptr时,argv的长度必须大于等于传入argc声明的大小。

当argv不为nullptr时,napi_get_cb_info会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量,该接口仅会将声明的argc数量的参数写入argv;而当argc大于实际参数数量时,该接口会在argv的尾部填充undefined。

错误示例

// argc未正确初始化,值为随机值,可能导致数据越界
static napi_value IncorrectDemo1(napi_env env, napi_callback_info info) {
size_t argc;
napi_value argv[10] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
return nullptr;
}

// argc声明数量大于argv实际初始化的长度
static napi_value IncorrectDemo2(napi_env env, napi_callback_info info) {
size_t argc = 5;
napi_value argv[3] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
return nullptr;
}

正确示例

//先传入nullptr获取真实参数数量,再动态分配数组
static napi_value GetArgvDemo1(napi_env env, napi_callback_info info) {
size_t argc = 0;
napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
if (argc == 0) {
return nullptr;
}
napi_value* argv = new napi_value[argc];
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
// 业务处理…
delete[] argv;
return nullptr;
}

// 声明argv长度 >= argc
static napi_value GetArgvDemo2(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
return nullptr;
}

二、生命周期管理

合理使用napi_open_handle_scope和napi_close_handle_scope管理napi_value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。

每个napi_value属于特定的HandleScope,HandleScope关闭后,所属的napi_value就会自动释放。

正确示例

// 在频繁创建JS对象的循环中,加handle_scope及时释放资源
for (int i = 0; i < 100000; i++) {
napi_handle_scope scope = nullptr;
napi_open_handle_scope(env, &scope);
if (scope == nullptr) {
return;
}
napi_value res;
napi_create_object(env, &res);
napi_close_handle_scope(env, scope);
}

三、上下文敏感

多引擎实例场景下,禁止通过Node-API跨引擎实例访问JS对象。

引擎实例是一个独立运行环境,JS对象创建访问等操作必须在同一个引擎实例中进行。引擎实例在接口中体现为napi_env。

错误示例

// 在env1创建string对象,在env2创建object并设置string属性
napi_create_string_utf8(env1, "bar", NAPI_AUTO_LENGTH, &string);
napi_create_object(env2, &object);
napi_set_named_property(env2, object, "foo", string);
// 在env2中访问env1的对象可能导致崩溃

说明:所有的JS对象都隶属于具体的某一napi_env,不可将env1的对象设置到env2的对象中。

四、异常处理

Node-API接口调用发生异常需要及时处理,不能遗漏异常到后续逻辑。

正确示例

//每步都检查返回值
napi_status status = napi_create_object(env, &object);
if (status != napi_ok) {
napi_throw_error(env, …);
return;
}
status = napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, &string);
if (status != napi_ok) {
napi_throw_error(env, …);
return;
}
status = napi_set_named_property(env, object, "foo", string);
if (status != napi_ok) {
napi_throw_error(env, …);
return;
}

只有当方法返回值是napi_ok时,才能继续正常运行;否则后续流程可能出现不可预期行为。

五、异步任务

使用uv_queue_work方法将任务抛到JS线程上执行时,对JS线程的回调方法需要加上napi_handle_scope管理napi_value生命周期。

说明:若只想往JS线程抛任务,不推荐使用uv_queue_work方法,请使用napi_threadsafe_function系列接口。

六、对象绑定

使用napi_wrap接口时,如果最后一个参数result传递不为nullptr,需要在合适时机调用napi_remove_wrap主动删除创建的napi_ref。

// 不需要接收napi_ref,传递nullptr
napi_wrap(env, jsobject, nativeObject, cb, nullptr, nullptr);

//需要接收napi_ref,返回强引用,需手动释放
napi_ref result;
napi_wrap(env, jsobject, nativeObject, cb, nullptr, &result);
// 后续及时调用napi_remove_wrap释放
void* nativeObjectResult = nullptr;
napi_remove_wrap(env, jsobject, &nativeObjectResult);

七、高性能数组

存储值类型数据时,使用ArrayBuffer代替JSArray来提高应用性能。

容器类型Benchmark数据(us)
JSArray 1566.174
ArrayBuffer 3.609

//使用ArrayBuffer作为容器
static napi_value ArrayBufferDemo(napi_env env, napi_callback_info info) {
constexpr size_t arrSize = 1000;
napi_value arrBuffer = nullptr;
void* data = nullptr;
napi_create_arraybuffer(env, arrSize * sizeof(int32_t), &data, &arrBuffer);
if (data == nullptr) {
return arrBuffer;
}
int32_t* i32Buffer = reinterpret_cast<int32_t*>(data);
for (int i = 0; i < arrSize; i++) {
i32Buffer[i] = i; // 直接操作缓冲区
}
return arrBuffer;
}

八、数据转换

建议说明
减少数据转换次数 批量处理数据或使用更高效的数据结构
避免不必要的数据复制 使用Node-API接口直接访问原始数据
使用缓存 多次转换中重复使用的数据可缓存

九、模块注册与模块命名

规则说明
nm_register_func 需加static修饰,防止符号冲突
模块注册入口函数名 确保与其他模块不同
nm_modname 需与so文件名完全匹配,区分大小写
一个so文件 只能注册一个模块

正确示例

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// …
return exports;
}
EXTERN_C_END

static napi_module nativeModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "nativerender", // 与so文件名完全匹配
.nm_priv = nullptr,
.reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterNativeRenderModule() {
napi_module_register(&nativeModule);
}

十、dlopen与模块注册

如果注册的模块事先有被dlopen,需使用以下方式注册模块。

模块需对外导出固定名称为napi_onLoad的函数,在该函数内调用注册函数。

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// …
return exports;
}
EXTERN_C_END

static napi_module nativeModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "nativerender",
.nm_priv = nullptr,
.reserved = { 0 },
};

extern "C" void napi_onLoad() {
napi_module_register(&nativeModule);
}

十一、napi_create_external系列接口使用规范

napi_create_external系列接口创建出来的JS对象仅允许在当前线程传递和使用,跨线程传递将会导致应用crash。

若需跨线程传递绑定有Native对象的JS对象,请使用napi_coerce_to_native_binding_object接口。

十二、防止重复释放获取的buffer

使用napi_get_arraybuffer_info等接口,参数data资源开发者不允许释放,data的生命周期受引擎管理。

受此规则约束的接口:

  • napi_create_arraybuffer

  • napi_create_sendable_arraybuffer

  • napi_get_arraybuffer_info

  • napi_create_buffer

  • napi_get_buffer_info

  • napi_get_typedarray_info

  • napi_get_dataview_info

错误示例

//不允许delete arrayBufferPtr
void* arrayBufferPtr = nullptr;
napi_value arrayBuffer = nullptr;
napi_create_arraybuffer(env, ARRAY_BUFFER_SIZE, &arrayBufferPtr, &arrayBuffer);
size_t arrayBufferSize;
napi_get_arraybuffer_info(env, arrayBuffer, &arrayBufferPtr, &arrayBufferSize);
delete arrayBufferPtr; // 禁止!会导致double free

十三、其他

合理使用napi_object_freeze和napi_object_seal控制对象可变性。

方法效果
napi_object_freeze 等同于Object.freeze,所有属性不可修改
napi_object_seal 等同于Object.seal,不可增删属性,但可改属性值

核心

规范项核心规则
参数获取 argv长度 >= argc声明大小
生命周期 HandleScope最小化,避免内存泄漏
上下文 禁止跨napi_env访问JS对象
异常处理 每步检查napi_ok,及时处理
异步任务 回调需加handle_scope
对象绑定 napi_wrap result非空需手动remove_wrap
高性能数组 值类型数据用ArrayBuffer
模块注册 nm_modname与so文件名完全匹配,一个so只注册一个模块
dlopen 导出napi_onLoad函数注册
buffer管理 data由引擎管理,禁止开发者释放
赞(0)
未经允许不得转载:171主机测评 » 鸿蒙 NDK开发:Node-API开发规范(六)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址