一、背景说明

鸿蒙应用的业务逻辑通常用 ArkTS 编写,但性能敏感场景(图像处理、加解密、音视频编解码、算法计算)往往需要复用已有的 C/C++ 代码库。NAPI(Node-API 的鸿蒙实现)是 ArkTS 与 Native C/C++ 之间的标准桥梁,通过它可以把 C++ 函数封装成 ArkTS 可直接调用的模块。近期华为 MatePad Edge 预置了"融合开发引擎"、支持在电脑模式直接运行 Linux 环境,也反映出鸿蒙生态对原生 C/C++ 开发的持续加码——本文聚焦应用侧最常用的 NAPI 混合开发完整流程。

二、技术原理

NAPI 模块的本质是一个动态库(.so),加载后向 ArkTS 运行时注册一组导出函数。整体数据流如下:

ArkTS 调用 → napi_env 上下文 → 参数解析(napi_get_cb_info)
→ C/C++ 计算 → 结果包装(napi_create_*) → 返回 ArkTS 值

三个关键概念:

  • napi_env / napi_value:napi_env 是当前 JS 执行环境的句柄,所有 NAPI 调用都依赖它;napi_value 是 JS 值在 C 侧的抽象,字符串、数字、对象统一用它表示。
  • 模块注册:C++ 侧通过 napi_property_descriptor 数组声明导出的方法名与回调,再用 napi_define_properties 绑定到 exports 对象,ArkTS 侧 import 后即可调用。
  • 异步与线程:耗时计算应使用 napi_create_async_work 把任务放入 libuv 线程池,避免阻塞 ArkTS 主线程(与 TaskPool 的思路一致,只是发生在 Native 层)。
三、实现步骤

下面以 DevEco Studio 的 Native C++ 模板工程为基础,实现一个字符串反转模块 mylib,并在 UI 中调用。

1. 工程结构
entry/
 ├── src/main/cpp/
 │    ├── CMakeLists.txt
 │    ├── napi_init.cpp        # NAPI 模块实现
 │    └── types/libentry/
 │         └── index.d.ts      # ArkTS 侧类型声明
 ├── src/main/ets/pages/
 │    └── Index.ets            # UI 调用示例
 └── build-profile.json5       # 模板已含 cpp 相关配置
2. C++ 模块实现(napi_init.cpp 完整代码)
#include "napi/native_api.h"
#include <algorithm>
#include <string>

// 同步版:字符串反转
static napi_value ReverseString(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    // 取出 ArkTS 传入的参数
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    size_t len = 0;
    // 先取字符串长度(UTF-8 字节数)
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
    std::string input(len + 1, '\0');
    napi_get_value_string_utf8(env, args[0], &input[0], len + 1, &len);
    input.resize(len);

    std::reverse(input.begin(), input.end());

    napi_value result = nullptr;
    napi_create_string_utf8(env, input.c_str(), input.size(), &result);
    return result;
}

// 异步版:求和(演示 napi_create_async_work,不阻塞主线程)
struct AddonData {
    napi_async_work work = nullptr;
    napi_deferred deferred = nullptr;
    double a = 0;
    double b = 0;
    double result = 0;
};

// 在工作线程执行,不能调用任何需要 env 的 JS API(result 除外)
static void ExecuteAdd(napi_env env, void *data) {
    AddonData *addon = reinterpret_cast<AddonData *>(data);
    double sum = 0;
    for (int i = 0; i < 1000000; i++) {          // 模拟耗时计算
        sum += addon->a * 0.000001 + addon->b * 0.000001;
    }
    addon->result = addon->a + addon->b + sum * 0.0;
}

// 计算完成,回到 JS 线程回调 Promise
static void CompleteAdd(napi_env env, napi_status status, void *data) {
    AddonData *addon = reinterpret_cast<AddonData *>(data);
    napi_value result = nullptr;
    napi_create_double(env, addon->result, &result);
    napi_resolve_deferred(env, addon->deferred, result);
    napi_delete_async_work(env, addon->work);
    delete addon;
}

static napi_value AddAsync(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    AddonData *addon = new AddonData();
    napi_get_value_double(env, args[0], &addon->a);
    napi_get_value_double(env, args[1], &addon->b);

    // 创建 Promise,把 deferred 存起来用于稍后 resolve
    napi_value promise = nullptr;
    napi_create_promise(env, &addon->deferred, &promise);

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addAsync", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, ExecuteAdd,
                           CompleteAdd, addon, &addon->work);
    napi_queue_async_work(env, addon->work);
    return promise;
}

// 模块注册信息
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"reverseString", nullptr, ReverseString, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"addAsync", nullptr, AddAsync, 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 = "entry",        // ArkTS 侧 import 的模块名
    .nm_priv = nullptr,
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
    napi_module_register(&demoModule);
}
3. CMakeLists.txt(完整代码)
cmake_minimum_required(VERSION 3.5.0)
project(myapp)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

include_directories(${NATIVERENDER_ROOT_PATH})

add_library(entry SHARED napi_init.cpp)

target_link_libraries(entry PUBLIC libace_napi.z.so)

注意:target_link_libraries 链接的 libace_napi.z.so 是系统提供的 NAPI 运行库,不需要额外下载。

4. ArkTS 类型声明(index.d.ts)
export const reverseString: (input: string) => string;
export const addAsync: (a: number, b: number) => Promise<number>;
5. UI 调用示例(Index.ets 完整代码)
import { reverseString, addAsync } from 'libentry.so';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State inputText: string = 'Hello HarmonyOS';
  @State result: string = '';

  build() {
    Column({ space: 16 }) {
      Text('NAPI 混合开发示例')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)

      TextInput({ text: this.inputText })
        .width('90%')
        .onChange((value: string) => {
          this.inputText = value;
        })

      Button('同步调用 reverseString')
        .width('90%')
        .onClick(() => {
          this.result = reverseString(this.inputText);
        })

      Button('异步调用 addAsync(3, 4)')
        .width('90%')
        .onClick(() => {
          addAsync(3, 4).then((sum: number) => {
            promptAction.showToast({ message: `计算结果:${sum}` });
          }).catch((err: Error) => {
            promptAction.showToast({ message: `出错:${err.message}` });
          });
        })

      Text(`结果:${this.result}`)
        .fontSize(18)
        .fontColor(Color.Gray)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

运行后点击第一个按钮,文本区显示 SOsnomyraH olleH;点击第二个按钮,Toast 弹出 计算结果:7,且主线程不卡顿。

四、应用场景示例

把图像降噪、国密加解密等已有 C 库接入鸿蒙应用时,只需在 napi_init.cpp 中 #include 原始头文件,在 Execute 回调里调用库函数,再把结果包装成 napi_value 返回。例如接入一个现成的哈希函数:

static napi_value Sha256Hex(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    size_t len = 0;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
    std::string input(len + 1, '\0');
    napi_get_value_string_utf8(env, args[0], &input[0], len + 1, &len);
    input.resize(len);

    std::string hex = MySha256Hex(input);   // 来自已有 C 库
    napi_value result = nullptr;
    napi_create_string_utf8(env, hex.c_str(), hex.size(), &result);
    return result;
}
五、技术要点
  1. 线程安全:Execute 回调运行在 libuv 工作线程,里面不能创建/操作 JS 对象,需要的数据在进入前拷贝进结构体,结果在 Complete 回调(JS 线程)里再包装。
  2. 同步接口的取舍:小数据量用同步版(延迟低);超过几毫秒的计算一律用 napi_create_async_work,否则会阻塞 ArkTS 主线程造成掉帧。
  3. 内存管理:napi_create_async_work 创建的对象要用 napi_delete_async_work 释放;new 出来的上下文在 Complete 里 delete,避免泄漏。
  4. 错误处理:参数类型不符时 napi_get_value_double 等会返回非 napi_ok,可用 napi_throw_type_error 抛给 ArkTS 侧的 catch:
if (napi_get_value_double(env, args[0], &addon->a) != napi_ok) {
    napi_throw_type_error(env, nullptr, "参数必须是 number");
    return nullptr;
}
  1. ABI 限制:模拟器与真机的 so 不通用,hvigor 会按 abiList(arm64-v8a / x86_64)分别编译,发布前确认目标设备 ABI 已包含在 build-profile.json5 中。
六、扩展建议

需要把复杂的 JS 对象传给 C++ 时,可使用 napi_get_named_property 逐字段读取,或用 napi_unwrap 配合 napi_wrap 实现 C++ 对象与 JS 对象的生命周期绑定:

// 把 C++ 对象绑定到 JS 对象,GC 时自动调用 finalize 释放
napi_wrap(env, jsObj, nativePtr,
    [](napi_env env, void *data, void *hint) { delete reinterpret_cast<MyObj *>(data); },
    nullptr, nullptr);

跨设备的场景下,还可以把 Native 模块与分布式能力结合——两端各自跑同一套 C++ 算法,中间用分布式数据对象同步结果。

结语

本文完整实现了一个含同步与异步接口的 NAPI 模块:C++ 侧约 100 行,ArkTS 侧调用只需一个 import。掌握参数解析、Promise 包装和异步工作队列这三板斧后,绝大多数已有 C/C++ 库都能低成本接入鸿蒙应用,兼顾性能与生态复用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐