Node-API 开发教程
1. 环境准备
1.1 安装必要工具
- 安装 HarmonyOS SDK 和 NDK
- 安装 Node.js (用于测试和开发)
- 安装 CMake (用于构建项目)
1.2 创建项目结构
css
my_napi_module/
├── CMakeLists.txt
├── include/
├── src/
│ └── main.cpp
└── package.json
2. 基础开发流程
2.1 创建简单的 Node-API 模块
src/main.cpp
arduino
#include <node_api.h>
// 原生函数实现
napi_value Hello(napi_env env, napi_callback_info info) {
napi_value greeting;
napi_status status = napi_create_string_utf8(
env, "Hello from Node-API!", NAPI_AUTO_LENGTH, &greeting);
if (status != napi_ok) return nullptr;
return greeting;
}
// 模块初始化函数
napi_value Init(napi_env env, napi_value exports) {
napi_status status;
napi_value fn;
// 创建JavaScript函数
status = napi_create_function(env, nullptr, 0, Hello, nullptr, &fn);
if (status != napi_ok) return nullptr;
// 将函数添加到exports对象
status = napi_set_named_property(env, exports, "hello", fn);
if (status != napi_ok) return nullptr;
return exports;
}
// 注册模块
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
2.2 配置 CMakeLists.txt
scss
cmake_minimum_required(VERSION 3.4.1)
project(my_napi_module)
# 设置编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra")
# 查找Node-API头文件
find_library(LIB_NODE node_api.h PATHS ${HarmonyOS_NDK}/sysroot/usr/lib)
# 添加共享库
add_library(my_napi_module SHARED src/main.cpp)
# 链接库
target_link_libraries(my_napi_module PUBLIC ${LIB_NODE})
3. 数据类型转换
3.1 JavaScript 到 C/C++
scss
napi_value Add(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 2) {
napi_throw_error(env, nullptr, "Expected 2 arguments");
return nullptr;
}
// 检查参数类型
napi_valuetype type1, type2;
napi_typeof(env, args[0], &type1);
napi_typeof(env, args[1], &type2);
if (type1 != napi_number || type2 != napi_number) {
napi_throw_type_error(env, nullptr, "Both arguments must be numbers");
return nullptr;
}
// 转换参数
double a, b;
napi_get_value_double(env, args[0], &a);
napi_get_value_double(env, args[1], &b);
// 创建返回值
napi_value result;
napi_create_double(env, a + b, &result);
return result;
}
3.2 C/C++ 到 JavaScript
scss
napi_value CreatePerson(napi_env env, napi_callback_info info) {
// 创建对象
napi_value person;
napi_create_object(env, &person);
// 添加属性
napi_value name;
napi_create_string_utf8(env, "Alice", NAPI_AUTO_LENGTH, &name);
napi_set_named_property(env, person, "name", name);
napi_value age;
napi_create_int32(env, 30, &age);
napi_set_named_property(env, person, "age", age);
// 添加方法
napi_value greet;
napi_create_function(env, nullptr, 0, [](napi_env env, napi_callback_info info) {
napi_value greeting;
napi_create_string_utf8(env, "Hello from person object!", NAPI_AUTO_LENGTH, &greeting);
return greeting;
}, nullptr, &greet);
napi_set_named_property(env, person, "greet", greet);
return person;
}
4. 异步操作
4.1 回调方式
ini
struct AsyncData {
napi_async_work work;
napi_threadsafe_function tsfn;
int input;
int result;
};
void ExecuteWork(napi_env env, void* data) {
AsyncData* async_data = static_cast<AsyncData*>(data);
// 模拟耗时操作
sleep(1);
async_data->result = async_data->input * 2;
}
void CompleteWork(napi_env env, napi_status status, void* data) {
AsyncData* async_data = static_cast<AsyncData*>(data);
// 调用JavaScript回调
napi_value undefined, argv[2];
napi_get_undefined(env, &undefined);
napi_get_null(env, &argv[0]);
napi_create_int32(env, async_data->result, &argv[1]);
napi_call_threadsafe_function(async_data->tsfn, argv, napi_tsfn_blocking);
napi_release_threadsafe_function(async_data->tsfn, napi_tsfn_release);
napi_delete_async_work(env, async_data->work);
delete async_data;
}
napi_value AsyncWithCallback(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 2) {
napi_throw_error(env, nullptr, "Expected 2 arguments");
return nullptr;
}
// 获取输入值和回调函数
int input;
napi_get_value_int32(env, args[0], &input);
// 创建线程安全函数
AsyncData* async_data = new AsyncData();
async_data->input = input;
napi_create_threadsafe_function(env, args[1], nullptr,
napi_create_string_utf8(env, "Callback", NAPI_AUTO_LENGTH),
0, 1, nullptr, nullptr, nullptr,
[](napi_env env, void* finalize_data, void* context) {},
&async_data->tsfn);
// 创建异步工作
napi_value resource_name = napi_create_string_utf8(env, "AsyncWork", NAPI_AUTO_LENGTH);
napi_create_async_work(env, nullptr, resource_name,
ExecuteWork, CompleteWork,
async_data, &async_data->work);
napi_queue_async_work(env, async_data->work);
return nullptr;
}
4.2 Promise 方式
ini
struct PromiseData {
napi_async_work work;
napi_deferred deferred;
int input;
int result;
};
void PromiseExecuteWork(napi_env env, void* data) {
PromiseData* promise_data = static_cast<PromiseData*>(data);
// 模拟耗时操作
sleep(1);
promise_data->result = promise_data->input * 2;
}
void PromiseCompleteWork(napi_env env, napi_status status, void* data) {
PromiseData* promise_data = static_cast<PromiseData*>(data);
napi_value result;
napi_create_int32(env, promise_data->result, &result);
napi_resolve_deferred(env, promise_data->deferred, result);
napi_delete_async_work(env, promise_data->work);
delete promise_data;
}
napi_value AsyncWithPromise(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1) {
napi_throw_error(env, nullptr, "Expected 1 argument");
return nullptr;
}
// 获取输入值
int input;
napi_get_value_int32(env, args[0], &input);
// 创建Promise
PromiseData* promise_data = new PromiseData();
promise_data->input = input;
napi_value promise;
napi_create_promise(env, &promise_data->deferred, &promise);
// 创建异步工作
napi_value resource_name = napi_create_string_utf8(env, "PromiseWork", NAPI_AUTO_LENGTH);
napi_create_async_work(env, nullptr, resource_name,
PromiseExecuteWork, PromiseCompleteWork,
promise_data, &promise_data->work);
napi_queue_async_work(env, promise_data->work);
return promise;
}
5. 错误处理
ini
napi_value Divide(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 2) {
napi_throw_error(env, nullptr, "Expected 2 arguments");
return nullptr;
}
// 检查参数类型
napi_valuetype type1, type2;
napi_typeof(env, args[0], &type1);
napi_typeof(env, args[1], &type2);
if (type1 != napi_number || type2 != napi_number) {
napi_throw_type_error(env, nullptr, "Both arguments must be numbers");
return nullptr;
}
double a, b;
napi_get_value_double(env, args[0], &a);
napi_get_value_double(env, args[1], &b);
if (b == 0) {
// 创建错误对象
napi_value error;
napi_create_error(env, nullptr,
napi_create_string_utf8(env, "Division by zero", NAPI_AUTO_LENGTH),
&error);
napi_throw(env, error);
return nullptr;
}
napi_value result;
napi_create_double(env, a / b, &result);
return result;
}
6. 构建和测试
6.1 构建模块
bash
mkdir build
cd build
cmake ..
make
6.2 JavaScript 测试代码
typescript
const path = require('path');
const addon = require('./build/Release/my_napi_module.node');
// 测试同步函数
console.log(addon.hello()); // "Hello from Node-API!"
console.log(addon.add(2, 3)); // 5
// 测试对象创建
const person = addon.createPerson();
console.log(person.name); // "Alice"
console.log(person.age); // 30
console.log(person.greet()); // "Hello from person object!"
// 测试异步回调
addon.asyncWithCallback(10, (err, result) => {
if (err) console.error(err);
else console.log(result); // 20
});
// 测试Promise
addon.asyncWithPromise(15)
.then(result => console.log(result)) // 30
.catch(err => console.error(err));
// 测试错误处理
try {
console.log(addon.divide(10, 0));
} catch (err) {
console.error(err.message); // "Division by zero"
}
7. 进阶主题
7.1 对象包装
scss
class MyClass {
public:
MyClass(double value) : value_(value) {}
double getValue() const { return value_; }
void setValue(double value) { value_ = value; }
private:
double value_;
};
// 包装原生对象
napi_value CreateWrapper(napi_env env, napi_callback_info info) {
double initialValue = 0.0;
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc > 0) {
napi_get_value_double(env, args[0], &initialValue);
}
// 创建原生对象
MyClass* obj = new MyClass(initialValue);
// 创建JavaScript对象
napi_value result;
napi_create_object(env, &result);
// 包装原生对象
napi_wrap(env, result, obj,
[](napi_env env, void* data, void* hint) {
delete static_cast<MyClass*>(data);
},
nullptr, nullptr);
return result;
}
// 访问包装对象的方法
napi_value GetValue(napi_env env, napi_callback_info info) {
napi_value thisArg;
napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr);
MyClass* obj;
napi_unwrap(env, thisArg, reinterpret_cast<void**>(&obj));
napi_value result;
napi_create_double(env, obj->getValue(), &result);
return result;
}
napi_value SetValue(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1) {
napi_throw_error(env, nullptr, "Expected 1 argument");
return nullptr;
}
napi_value thisArg;
napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr);
MyClass* obj;
napi_unwrap(env, thisArg, reinterpret_cast<void**>(&obj));
double value;
napi_get_value_double(env, args[0], &value);
obj->setValue(value);
return nullptr;
}
7.2 使用 TypedArray 和 ArrayBuffer
ini
napi_value ProcessBuffer(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1) {
napi_throw_error(env, nullptr, "Expected 1 argument");
return nullptr;
}
// 检查是否为TypedArray
bool is_typedarray;
napi_is_typedarray(env, args[0], &is_typedarray);
if (!is_typedarray) {
napi_throw_type_error(env, nullptr, "Expected a TypedArray");
return nullptr;
}
// 获取TypedArray信息
napi_typedarray_type type;
size_t length;
void* data;
napi_value arraybuffer;
size_t byte_offset;
napi_get_typedarray_info(env, args[0], &type, &length, &data,
&arraybuffer, &byte_offset);
if (type != napi_uint8_array) {
napi_throw_type_error(env, nullptr, "Expected Uint8Array");
return nullptr;
}
// 处理数据
uint8_t* bytes = static_cast<uint8_t*>(data);
for (size_t i = 0; i < length; i++) {
bytes[i] = bytes[i] * 2; // 简单处理
}
return args[0]; // 返回修改后的TypedArray
}
8. 调试技巧
- 日志输出 :使用
printf
或console.log
调试 - 错误检查:每次 Node-API 调用后检查状态
- 使用调试器:GDB 或 LLDB 调试原生代码
- 内存检查:使用 Valgrind 检查内存问题
- 性能分析:使用性能分析工具优化关键代码
9. 最佳实践
- 最小化跨语言调用:减少 JavaScript 和 C++ 之间的边界调用
- 批量处理数据:使用 TypedArray 或 ArrayBuffer 传输大量数据
- 错误处理:全面检查错误并妥善处理
- 资源清理:确保释放所有分配的资源
- 线程安全:了解哪些操作必须在主线程执行
- 文档注释:为原生函数添加详细文档
通过本教程,您应该已经掌握了 Node-API 开发的基础知识和常见模式。实际开发中,建议参考官方文档获取最新的 API 信息和更高级的特性。