鸿蒙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 信息和更高级的特性。

相关推荐
aqi003 小时前
鸿蒙版本的JSBridge兼容与安卓配套的H5啦
android·华为·harmonyos·鸿蒙·移动应用
熊猫钓鱼>_>4 小时前
Flutter app_settings 鸿蒙适配实战:Intent 体系到 Want 的跨越
flutter·华为·harmonyos·openharmony·intent·want
贾伟康7 小时前
【句匠|20】HarmonyOS ArkTS AppGallery 发布复查实战:核对包名、版本、设备、素材和离线声明
harmonyos·arkts·应用上架·appgallery·发布审核
贾伟康8 小时前
【句匠|15】HarmonyOS ArkTS 本地状态持久化实战:让保存、删除和页面返回后的数据即时一致
harmonyos·arkts·数据持久化·appstorage·preferences
贾伟康10 小时前
【句匠|16】HarmonyOS ArkTS 多设备布局实战:适配手机、平板和 PC/2in1 的窗口变化
harmonyos·arkts·arkui·响应式布局·多设备适配
小雨青年11 小时前
【HarmonyOS 7 平行视界深度实战】03 购物模式怎么实现连续浏览和左右推挤
华为·harmonyos
SuperHeroWu713 小时前
HarmonyOS Dev Assistant (HarmonyOS开发助手)如何打通元服务开发全流程
ai·agent·harmonyos·vs code·元服务·hbuilderx·assistant
熊猫钓鱼>_>13 小时前
用 OHAudioSuite 空间渲染节点搭一座「3D 有声博物馆」——从三种模式到 FreeBuds 头追的实战记录
c++·3d·ai·harmonyos·arkts·audio·ohaudiosuite
OH_TPC13 小时前
HarmonyOS APP开发---"心动卡"社交匹配App,需要用到这个库
harmonyos
光锥智能13 小时前
HUAWEI WATCH 6系列正式发布:鸿蒙AI手表,智慧健康旗舰
人工智能·华为·harmonyos