Skip to content

NAPI(Native API)是 OpenHarmony/HarmonyOS 系统中的原生模块接口,它允许 JavaScript/ArkTS 代码调用 C/C++ 原生代码,提供高性能计算能力,访问系统底层能力,并复用现有的 C/C++ 库。

📚 什么是 NAPI?

NAPI(Native API)是 OpenHarmony/HarmonyOS 系统中的原生模块接口,它允许:

  • JavaScript/ArkTS 代码调用 C/C++ 原生代码
  • 提供高性能计算能力(如 AI 推理)
  • 访问系统底层能力
  • 复用现有的 C/C++ 库

🎯 NAPI 的核心价值

1. 性能提升

通过调用原生代码,可以获得接近原生的性能,特别适合:

  • 图像处理
  • 加密解密
  • 数据压缩
  • AI 模型推理
  • 大量计算密集型任务

2. 系统能力访问

可以访问 HarmonyOS 系统底层能力:

  • 硬件抽象层(HAL)
  • 驱动接口
  • 系统服务
  • 设备管理

3. 代码复用

可以复用现有的 C/C++ 库:

  • OpenCV(图像处理)
  • TensorFlow Lite(AI 推理)
  • FFmpeg(音视频处理)
  • SQLite(数据库)
  • 其他成熟的 C/C++ 库

🔧 NAPI 开发环境

前置要求

  • DevEco Studio 4.0+
  • HarmonyOS SDK 4.0+
  • C/C++ 编译器(CMake)
  • NDK(Native Development Kit)

项目结构

text
MyApplication
├── entry
│   ├── src
│   │   ├── main
│   │   │   ├── cpp
│   │   │   │   ├── CMakeLists.txt      # C/C++ 构建配置
│   │   │   │   └── native_module.cpp   # 原生模块实现
│   │   │   ├── ets
│   │   │   │   └── pages
│   │   │   │       └── Index.ets       # ArkTS 调用代码
│   │   │   └── resources

💻 快速开始

1. 创建 NAPI 模块

entry/src/main/cpp/ 目录下创建 native_module.cpp

cpp
#include "napi/native_api.h"

// 导出函数:计算两个数的和
static 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);

    // 获取参数
    double a, b;
    napi_get_value_double(env, args[0], &a]);
    napi_get_value_double(env, args[1], &b);

    // 计算结果
    double result = a + b;

    // 返回结果
    napi_value returnValue;
    napi_create_double(env, result, &returnValue);
    return returnValue;
}

// 模块导出
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "native_module",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    napi_module_register(&demoModule);
}

2. 配置 CMakeLists.txt

entry/src/main/cpp/ 目录下创建 CMakeLists.txt

cmake
cmake_minimum_required(VERSION 3.4.1)
project(native_module)

add_library(native_module SHARED
    native_module.cpp
)

target_link_libraries(native_module PUBLIC libace_napi.z.so)

3. ArkTS 调用原生模块

entry/src/main/ets/pages/Index.ets 中:

typescript
import native_module from 'libnative_module.so';

@Entry
@Component
struct Index {
  @State result: number = 0;

  build() {
    Column() {
      Text('NAPI 示例')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      Button('计算 10 + 20')
        .onClick(() => {
          try {
            this.result = native_module.add(10, 20);
          } catch (error) {
            console.error('调用原生模块失败:', error);
          }
        })
        .margin({ bottom: 10 })

      Text(`结果: ${this.result}`)
        .fontSize(20)
        .fontColor('#4A90E2')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

📦 .so 文件详解

什么是 .so 文件?

.so 文件是 **Shared Object(共享对象)**的缩写,在 Linux/Unix/HarmonyOS 系统中相当于 Windows 中的 .dll(动态链接库)文件。

文件类型对比

文件类型平台描述
.soLinux/Unix/Android/HarmonyOS动态链接库
.dllWindows动态链接库
.aLinux/Unix静态链接库
.libWindows静态链接库

动态库 vs 静态库

静态库 (.a)

  • ✅ 编译时直接嵌入到可执行文件中
  • ✅ 部署简单,不依赖外部库
  • ❌ 文件体积较大
  • ❌ 更新时需要重新编译整个应用

动态库 (.so)

  • ✅ 运行时才加载
  • ✅ 多个程序可共享同一个库
  • ✅ 文件体积较小
  • ✅ 库更新无需重新编译应用
  • ⚠️ 需要确保目标系统有相应的库

在 HarmonyOS 中使用 .so 文件

1. 编译生成 .so 文件

通过 CMake 配置编译为动态库:

cmake
cmake_minimum_required(VERSION 3.4.1)
project(native_module)

# 编译为动态库(SHARED)
add_library(native_module SHARED
    native_module.cpp
)

# 链接系统库
target_link_libraries(native_module PUBLIC libace_napi.z.so)

编译后会生成:

  • libnative_module.so - 动态库文件
  • 位于 entry/build/default/intermediates/libs/arm64-v8a/ 目录

2. 在 ArkTS 中导入 .so 文件

typescript
// 导入动态库
import native_module from "libnative_module.so";

// 调用原生函数
const result = native_module.add(10, 20);

3. .so 文件命名规则

HarmonyOS 中的 .so 文件命名:

text
lib<模块名>.so

例如:

  • libnative_module.so - 原生模块
  • libace_napi.z.so - NAPI 系统库
  • libc++.so - C++ 标准库

.so 文件的优势

  1. 模块化开发:将功能封装为独立模块
  2. 代码复用:多个应用可共享同一个库
  3. 按需加载:只在需要时加载,节省内存
  4. 独立更新:库更新不影响应用主体
  5. 性能优化:原生代码执行效率高

常见问题

Q: .so 文件找不到怎么办?

typescript
// ❌ 错误:路径不正确
import module from "native_module.so";

// ✅ 正确:使用 lib 前缀
import module from "libnative_module.so";

Q: 如何调试 .so 文件?

bash
# 查看 .so 文件信息
readelf -d libnative_module.so

# 查看符号表
nm -D libnative_module.so

# 使用 ldd 查看依赖(Linux)
ldd libnative_module.so

Q: 如何减小 .so 文件体积?

cmake
# 使用 Release 模式编译
set(CMAKE_BUILD_TYPE Release)

# 启用链接时优化
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto")

# 去除调试信息
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s")

多架构支持

HarmonyOS 支持多种 CPU 架构:

text
entry/build/default/intermediates/libs/
├── arm64-v8a/          # 64 位 ARM
│   └── libnative_module.so
├── armeabi-v7a/        # 32 位 ARM
│   └── libnative_module.so
└── x86_64/             # 64 位 x86
    └── libnative_module.so

build-profile.json5 中配置:

json5
{
  apiType: "stageMode",
  buildOption: {
    arkOptions: {
      runtimeOnly: false,
    },
  },
  targets: [
    {
      name: "default",
      compileSdkVersion: 9,
      compatibleSdkVersion: 9,
    },
  ],
}

📖 NAPI 常用 API

数据类型转换

JavaScript → C/C++

cpp
// 获取数字
double value;
napi_get_value_double(env, jsValue, &value);

// 获取字符串
size_t strLen;
napi_get_value_string_utf8(env, jsValue, nullptr, 0, &strLen);
char* buffer = new char[strLen + 1];
napi_get_value_string_utf8(env, jsValue, buffer, strLen + 1, &strLen);

// 获取布尔值
bool boolValue;
napi_get_value_bool(env, jsValue, &boolValue);

C/C++ → JavaScript

cpp
// 创建数字
napi_value jsNumber;
napi_create_double(env, 3.14, &jsNumber);

// 创建字符串
napi_value jsString;
napi_create_string_utf8(env, "Hello", NAPI_AUTO_LENGTH, &jsString);

// 创建布尔值
napi_value jsBool;
napi_get_boolean(env, true, &jsBool);

对象操作

cpp
// 创建对象
napi_value obj;
napi_create_object(env, &obj);

// 设置属性
napi_value propValue;
napi_create_string_utf8(env, "value", NAPI_AUTO_LENGTH, &propValue);
napi_set_named_property(env, obj, "name", propValue);

// 获取属性
napi_value prop;
napi_get_named_property(env, obj, "name", &prop);

数组操作

cpp
// 创建数组
napi_value arr;
napi_create_array(env, &arr);

// 设置数组元素
napi_value element;
napi_create_double(env, 1.0, &element);
napi_set_element(env, arr, 0, element);

// 获取数组长度
uint32_t length;
napi_get_array_length(env, arr, &length);

// 获取数组元素
napi_value element;
napi_get_element(env, arr, 0, &element);

🎨 实战案例

案例 1:图像处理

cpp
#include "napi/native_api.h"
#include <opencv2/opencv.hpp>

static napi_value ProcessImage(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);

    // 从 JavaScript 获取图像数据
    uint8_t* imageData;
    size_t length;
    napi_get_buffer_info(env, args[0], (void**)&imageData, &length);

    // 使用 OpenCV 处理图像
    cv::Mat image = cv::imdecode(cv::Mat(1, length, CV_8UC1, imageData), cv::IMREAD_COLOR);
    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);

    // 返回处理后的图像数据
    std::vector<uchar> buffer;
    cv::imencode(".jpg", gray, buffer);

    napi_value result;
    napi_create_buffer_copy(env, buffer.size(), buffer.data(), nullptr, &result);
    return result;
}

案例 2:AI 推理

cpp
#include "napi/native_api.h"
#include "tensorflow/lite/interpreter.h"

static napi_value RunInference(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);

    float* inputData;
    size_t length;
    napi_get_arraybuffer_info(env, args[0], (void**)&inputData, &length);

    // 运行 AI 推理
    // ... TensorFlow Lite 推理代码 ...

    // 返回结果
    napi_value result;
    napi_create_arraybuffer(env, outputSize * sizeof(float), nullptr, &result);
    return result;
}

⚠️ 注意事项

1. 内存管理

cpp
// ❌ 错误:忘记释放内存
char* buffer = new char[1024];
// 使用 buffer
// 忘记 delete[]

// ✅ 正确:使用智能指针或 RAII
std::unique_ptr<char[]> buffer(new char[1024]);
// 自动释放

2. 异常处理

cpp
// 检查 NAPI 调用是否成功
napi_status status = napi_get_value_double(env, value, &result);
if (status != napi_ok) {
    // 处理错误
    napi_throw_error(env, nullptr, "Failed to get double value");
    return nullptr;
}

3. 线程安全

cpp
// NAPI 不是线程安全的
// 不要在多线程中直接调用 NAPI API

// 如果需要在后台线程工作
// 使用 napi_create_async_work 创建异步任务

📚 进阶主题

异步操作

cpp
struct AsyncData {
    napi_async_work work;
    napi_deferred deferred;
    // 其他数据
};

static void AsyncWorkExecute(napi_env env, void* data)
{
    AsyncData* asyncData = (AsyncData*)data;
    // 在后台线程执行耗时操作
}

static void AsyncWorkComplete(napi_env env, napi_status status, void* data)
{
    AsyncData* asyncData = (AsyncData*)data;
    // 返回结果给 JavaScript
    napi_resolve_deferred(env, asyncData->deferred, result);
    napi_delete_async_work(env, asyncData->work);
    delete asyncData;
}

回调函数

cpp
static napi_value CallCallback(napi_env env, napi_callback_info info)
{
    // 获取回调函数
    napi_value callback;
    napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &callback);

    // 调用回调
    napi_value result;
    napi_create_string_utf8(env, "Hello from native", NAPI_AUTO_LENGTH, &result);
    napi_value argv[1] = { result };
    napi_value global;
    napi_get_global(env, &global);
    napi_call_function(env, global, callback, 1, argv, nullptr);

    return nullptr;
}

🔗 相关资源

官方文档

示例代码

推荐阅读

💡 最佳实践

  1. 性能优先:只在需要高性能或系统能力时使用 NAPI
  2. 错误处理:始终检查 NAPI 调用的返回值
  3. 内存管理:使用智能指针或 RAII 模式
  4. 类型安全:使用 TypeScript 类型定义
  5. 测试覆盖:为原生模块编写单元测试

🦀 Rust NAPI 模块语法

导入语句解析

rust
use napi::bindgen_prelude::*;
use napi::{Env, JsObject, Result};
use napi_derive::napi;
use napi_derive::{module_exports, napi};

1. use napi::bindgen_prelude::*;

作用:导入 NAPI 的预绑定模块,包含常用的类型和宏

包含的内容

  • JsFunction - JavaScript 函数类型
  • JsNumber - JavaScript 数字类型
  • JsString - JavaScript 字符串类型
  • JsObject - JavaScript 对象类型
  • JsArray - JavaScript 数组类型
  • JsBuffer - JavaScript 缓冲区类型
  • JsPromise - JavaScript Promise 类型
  • JsUndefined - JavaScript undefined 类型
  • JsNull - JavaScript null 类型
  • JsBoolean - JavaScript 布尔类型
  • JsDate - JavaScript 日期类型
  • JsError - JavaScript 错误类型

使用示例

rust
use napi::bindgen_prelude::*;

#[napi]
fn add(a: f64, b: f64) -> f64 {
    a + b
}

2. use napi::{Env, JsObject, Result};

作用:导入 NAPI 核心类型

包含的内容

  • Env - NAPI 环境句柄,用于访问 JavaScript 环境
  • JsObject - JavaScript 对象包装器
  • Result<T> - NAPI 操作的结果类型

使用示例

rust
use napi::{Env, JsObject, Result};

#[napi]
fn create_object(env: Env) -> Result<JsObject> {
    let mut obj = env.create_object()?;
    obj.set_named_property("name", env.create_string("Hello")?)?;
    Ok(obj)
}

3. use napi_derive::napi;

作用:导入 napi 过程宏,用于自动生成 NAPI 绑定代码

功能

  • 自动生成函数导出代码
  • 自动处理类型转换
  • 自动生成错误处理

使用示例

rust
use napi_derive::napi;

#[napi]
fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

4. use napi_derive::{module_exports, napi};

作用:导入模块导出宏

功能

  • napi - 标记函数为 NAPI 导出函数
  • module_exports - 标记模块导出函数

使用示例

rust
use napi_derive::{module_exports, napi};

// 导出函数
#[napi]
fn add(a: f64, b: f64) -> f64 {
    a + b
}

// 导出模块
#[module_exports]
fn init(module: &mut JsObject) -> Result<()> {
    module.create_named_method("add", add)?;
    Ok(())
}

完整的 Rust NAPI 模块示例

rust
use napi::bindgen_prelude::*;
use napi::{Env, JsObject, Result};
use napi_derive::napi;

// 导出普通函数
#[napi]
fn add(a: f64, b: f64) -> f64 {
    a + b
}

// 导出异步函数
#[napi]
async fn async_add(a: f64, b: f64) -> f64 {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    a + b
}

// 导出结构体
#[napi]
struct Calculator {
    value: f64,
}

#[napi]
impl Calculator {
    #[napi(constructor)]
    pub fn new(value: f64) -> Self {
        Calculator { value }
    }

    #[napi]
    pub fn add(&mut self, n: f64) -> f64 {
        self.value += n;
        self.value
    }

    #[napi]
    pub fn get_value(&self) -> f64 {
        self.value
    }
}

// 导出常量
#[napi]
const PI: f64 = 3.141592653589793;

// 导出枚举
#[napi]
enum Operation {
    Add,
    Subtract,
    Multiply,
    Divide,
}

#[napi]
fn calculate(op: Operation, a: f64, b: f64) -> f64 {
    match op {
        Operation::Add => a + b,
        Operation::Subtract => a - b,
        Operation::Multiply => a * b,
        Operation::Divide => a / b,
    }
}

Rust vs C++ NAPI 对比

特性C++Rust
内存安全手动管理编译器保证
类型系统手动转换自动推导
宏支持有限强大的过程宏
异步支持手动实现原生 async/await
错误处理手动检查Result 类型
代码生成手动编写自动生成

Rust NAPI 的优势

  1. 内存安全:编译器保证,无需手动管理
  2. 类型安全:编译时检查,减少运行时错误
  3. 自动转换:宏自动处理 JavaScript 和 Rust 类型转换
  4. 异步支持:原生支持 async/await
  5. 错误处理:Result 类型统一处理错误

在 HarmonyOS 中使用 Rust NAPI

1. 配置 Cargo.toml

toml
[package]
name = "native_module"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
napi = { version = "2", features = ["tokio_rt"] }
napi-derive = "2"
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }

2. 构建配置

bash
# 安装 Rust 工具链
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 添加目标架构
rustup target add aarch64-linux-android

# 构建 .so 文件
cargo build --target aarch64-linux-android --release

3. ArkTS 调用

typescript
import native_module from "libnative_module.so";

// 调用 Rust 函数
const result = native_module.add(10, 20);
console.log(result); // 30

// 使用 Rust 结构体
const calc = new native_module.Calculator(10);
calc.add(5);
console.log(calc.getValue()); // 15

// 调用异步函数
const asyncResult = await native_module.asyncAdd(10, 20);
console.log(asyncResult); // 30

Rust NAPI 常见问题

Q: Rust NAPI 和 C++ NAPI 可以混用吗?

rust
// ✅ 可以,但需要分别编译
// C++ 模块
// libnative_module_cpp.so

// Rust 模块
// libnative_module_rust.so

Q: 如何处理 Rust 错误?

rust
use napi::Result;

#[napi]
fn divide(a: f64, b: f64) -> Result<f64> {
    if b == 0.0 {
        return Err(napi::Error::from_reason("Division by zero"));
    }
    Ok(a / b)
}

Q: 如何导出复杂类型?

rust
use napi::bindgen_prelude::*;

#[napi]
struct Point {
    x: f64,
    y: f64,
}

#[napi]
impl Point {
    #[napi]
    fn distance(&self, other: &Point) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
}

🎓 学习路径

初学者

  1. 学习 C/C++ 基础
  2. 了解 NAPI 基本 API
  3. 实现简单的计算函数
  4. 学习数据类型转换

进阶开发者

  1. 掌握异步操作
  2. 学习回调函数
  3. 集成第三方库
  4. 性能优化技巧
  5. 学习 Rust NAPI(新增)

专家级

  1. 自定义数据结构
  2. 复杂内存管理
  3. 多线程编程
  4. 系统级优化
  5. Rust 与 C++ 混合开发(新增)

让我们一起探索 HarmonyOS 原生开发的无限可能! 🚀

本站总访问