鸿蒙Node-API 开发教程

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. 调试技巧

  1. 日志输出 :使用 printfconsole.log调试
  2. 错误检查:每次 Node-API 调用后检查状态
  3. 使用调试器:GDB 或 LLDB 调试原生代码
  4. 内存检查:使用 Valgrind 检查内存问题
  5. 性能分析:使用性能分析工具优化关键代码

9. 最佳实践

  1. 最小化跨语言调用:减少 JavaScript 和 C++ 之间的边界调用
  2. 批量处理数据:使用 TypedArray 或 ArrayBuffer 传输大量数据
  3. 错误处理:全面检查错误并妥善处理
  4. 资源清理:确保释放所有分配的资源
  5. 线程安全:了解哪些操作必须在主线程执行
  6. 文档注释:为原生函数添加详细文档

通过本教程,您应该已经掌握了 Node-API 开发的基础知识和常见模式。实际开发中,建议参考官方文档获取最新的 API 信息和更高级的特性。

相关推荐
万少7 小时前
可可图片编辑 HarmonyOS(4)图片裁剪-canvas
前端·harmonyos
爱笑的眼睛1111 小时前
HarmonyOS 应用开发:深入解析 ArkTS 并发编程与最佳实践
华为·harmonyos
森之鸟14 小时前
开发中使用——鸿蒙本地存储之收藏功能
java·华为·harmonyos
安卓开发者16 小时前
鸿蒙NEXT界面交互全解析:弹出框、菜单、气泡提示与模态页面的实战指南
华为·交互·harmonyos
HarmonyOS小助手1 天前
H5 页面加载终于不转圈了!FastWeb 组件让加载快到起飞
harmonyos·鸿蒙·鸿蒙生态
Georgewu1 天前
【HarmonyOS 6】仿AI唤起屏幕边缘流光特效
harmonyos
Georgewu1 天前
【 HarmonyOS 6 】HarmonyOS智能体开发实战:Function组件和智能体创建
harmonyos
SuperHeroWu71 天前
【HarmonyOS】一步解决弹框集成-快速弹框QuickDialog使用详解
华为·harmonyos
万少1 天前
可可图片编辑 HarmonyOS(3)应用间分享图片
前端·harmonyos·客户端