NAPI篇【2】——OpenHarmony含NAPI工程cpp详解

        NAPI篇【1】中,讲解如何创建一个支持NAPI的OpenHarmony工程,其中有一个步骤是在OpenHarmony工程添加Harmony的NAPI工程中的cpp文件夹。cpp文件夹包含支持NAPI所需要的c/c++源码、接口导出文件以及编译文件等。本文将进行详解其中各文件的作用,帮助熟悉NAPI的开发。

        本文基于笔者的开发经验和网上参考资料汇集而成,笔者也处于学习阶段,如有错误,欢迎指正。

一、cpp文件夹——基础文件

        1、cpp文件夹基本内容

        通过NAPI模板新建工程,cpp文件夹内容全展开,如下图所示。

        2、cpp的一级目录文件

         如下图所示,cpp的一级目录下有hello.cpp、CMakeLists.txt 和一个二级目录types。

        (1)CMakeLists.txt 文件

        CMakeLists.txt是cmake用来生成Makefile文件的一个描述编译链接的脚本文件。该文件的基本内容行注释如下所示:

#CMake的最低版本要求
#默认即可
cmake_minimum_required(VERSION 3.4.1)    
 
# 用函数project(demo)指定的项目名称,可自行设置,这里变量的值为“napi”
project(napi)           

#设置环境变量,设置库文件的输出地址。
#CMAKE_CURRENT_SOURCE_DIR:当前处理的CMakeLists.txt文件所在目录
#默认即可
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})


#给工程添加头文件,会为当前CMakeLists.txt的所有目标,
#以及之后添加的所有子目录的目标添加指定头文件搜索路径。
#默认即可
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

add_library(entry SHARED hello.cpp)   
#将源文件hello.cpp生成动态库文件,若有多个cpp文件,需要在此添加,以便生成动态库文件
# 生成目标库文件libentry.so,entry表示最终的库名称,SHARED表示生成的是动态链接库,
#////////////////////////////////////////////////////
#add_library(生成的库名称 STATIC/SHARED 源文件1.cpp 源文件2.cpp)
#将源文件生成 静态/动态 库文件 
#STSTIC 表示静态库最终会编入到可执行文件中
#SHARED(常见参数)表示动态库(又称共享库)方式
#////////////////////////////////////////////////////

target_link_libraries(entry PUBLIC libace_napi.z.so)
# 把libentry.so链接到libace_napi.z.so上
#/////////////////////////////////////////////
#target_link_libraries (库/可执行文件 library1 library2 ...)
#将目标文件(为库或者可执行文件)与库文件进行链接
#libace_napi.z.so库,默认即可
#/////////////////////////////////////////////

        CMakeLists.txt 文件需要在 build-profile.json5 文件里做配置声明,代码如下所示,对应 NAPI篇【1】中的   二、2、(3)步。

{
  "apiType": 'stageMode',
  "buildOption": {
//在buildOption配置项中
//声明添加NAPI的编译目标,对CMakeLists.txt编译脚本进行声明执行
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "arguments": "",
      "cppFlags": "",
    }
/////////////////////////////////////////////////////////
  },
  "targets": [
    {
      "name": "default"
    },
    {
      "name": "ohosTest",
    }
  ]
}

        (2).cpp源文件

        在cpp文件夹的一级目录文件下,可以有多个.cpp源代码文件,比如,加一个fun.cpp文件,这些文件用来实现某一功能,带需要在CMakeLists.txt 文件中,add_library(生成的库名称 STATIC/SHARED 源文件1.cpp 源文件2.cpp)这一函数中进行声明,方便参与生成库文件。

        在cpp文件夹的一级目录文件下,还以包含.h的头文件,方便多个.cpp文件的编写,相互调用。如下图所示:

        (3) NAPI函数的定义与注册(hello.cpp解读)

        利用NAPI模板生成的工程中,默认生成的hello.cpp文件中包含了NAPI函数编写、定义与注册,文本借助hello.cpp文件的解读,来梳理。(NAPI函数的编写,详见NAPI篇【3】)

        cpp 目录下默认生成的 hello.cpp 文件,源码如下所示:

#include "napi/native_api.h"

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

    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);

    return sum;

}

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 = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}
         (3.1)注册napi模块
static napi_module demoModule = {
    .nm_version =1,//nm_version:nm版本号,默认值为 1。
    .nm_flags = 0,//nm标记符,默认值为 0。
    .nm_filename = nullptr,//暂不关注,使用默认值即可。
    .nm_register_func = Init,//指定nm的入口函数,即napi定义模块
    .nm_modname = "entry",//指定 TS 页面导入的模块名,例如:`import testNapi from 'libentry.so'` 中的 testNapi 
  //就是当前的nm_modname。
    .nm_priv = ((void*)0),//暂不关注,使用默认值即可。
    .reserved = { 0 },//暂不关注,使用默认值即可。
};

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

//extern "C" 简单理解就是告诉编译器这部分代码按照 C 语言进行编译而不是 C++ 语言编译。
//__attribute__((constructor)) 声明方法的执行时机,它表示 RegisterEntryModule() 方法在 main() 
//方法执行前执行, RegisterEntryModule() 方法内调用了 napi_module_register() 方法,该方法是 
//NAPI 提供的模块注册方法,表示把定义的 demoModule 模块注册到系统中。
        (3.2)定义napi模块
EXTERN_C_START   //开始标志
static napi_value Init(napi_env env, napi_value exports)  //Init定义模块名
{
    napi_property_descriptor desc[] = {
  //需要引出的NAPI功能函数,这以Add函数为例,"add"对应 libentry 目录下index.d.ts 文件中
   //export const add: (a: number, b: number) => number;导出的函数名
//"add"自定义函数名,用于ets调用,注意与index.d.ts 文件中导出名一致;Add注册定义的函数名。
        { "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    //结束标志

3、二级目录types

    (1) 目录libentry基本内容

        目录types包含有一个三级目录libentry

4、三级目录libentry

      (1)目录libentry基本内容

        目录libentry包含有文件index.d.ts和oh-package.json5

        (2)index.d.ts文件

        libentry 目录下生成了 index.d.ts 文件,用来声明要导出的NAPI函数,它的源码如下所示:

export const add: (a: number, b: number) => number;

        export const 表示导出一个常量以便在其它文件中使用。add 是一个返回类型为 number 的方法,它的参数类为 number 类型。

        (3)oh-package.json5文件

        libentry目录下生成了 package.json 文件,该文件是打包的配置文件,内容如下所示:

{
  "name": "libentry.so",
  "types": "./index.d.ts",
  "version": "",
  "description": "Please describe the basic information."
}

        设置 libentry.so 库和 index.d.ts 相关联,便于在 TS 文件中引入 libentry.so 时调用库中的相关方法。添加了d.ts模块,要想被ets在静态时检查到,需要配置entry/oh-package.json5下配置dependencies,对应NAPI篇【1】中的   二、2、(4)步。

二、NAPI函数的调用

        1、模块导入

        根据前边的编译配置,cpp 目录下的源码最终打包成了 libentry.so,因此,在ets或ts文件中使用前直接引入即可。例如:

import testNapi from 'libentry.so'

        2、函数调用

        引入 libentry.so 模块后,就可以直接调用在index.d.ts文件声明的NAPI函数,填入相应的参数即可,例如调用 add() 函数:

 

        参考链接:

        (1)CMakeLists.txt详解-优快云博客

        (2)CMakeLists.txt常用指令总结与练习_创建cmakelists.txt.指令-优快云博客

        (3)【坚果派-坚果】Napi入门_libnapi-优快云博客

        (4)openHarmony使用Napi开发样例_napi 中如何引入 静态库-优快云博客

10-03
<think>我们正在讨论OpenHarmony开发板完成内核移植后的应用开发,特别是关于NAPI(Native API)的相关知识。NAPIOpenHarmony中用于JavaScript与C/C++代码交互的接口,允许开发者用C/C++编写高性能模块,并在JavaScript(如ArkTS)中调用。 根据引用资料,NAPI被列为OpenHarmony开发的核心知识点之一[^1][^2]。下面将系统性地介绍NAPIOpenHarmony应用开发中的关键内容: --- ### **一、NAPI的核心作用与架构** NAPI作为ArkTS与C/C++的桥梁,其架构如下: ```mermaid graph LR A[ArkTS应用] -->|调用| B(NAPI模块) B --> C[C/C++本地代码] C -->|操作| D[系统硬件/HDF驱动] ``` - **优势**: - 性能关键代码(如音视频处理)用C++实现,性能提升$3\times$以上[^2] - 直接访问底层硬件(如GPIO、I2C) --- ### **二、NAPI模块开发流程** #### 1. 环境配置 在`build-profile.json5`中添加NDK依赖: ```json "buildOption": { "externalNativeOptions": { "path": "./src/main/cpp/CMakeLists.txt" // 指向C++工程 } } ``` #### 2. 创建NAPI模块 **步骤1:定义ArkTS接口** `entry/src/main/ets/MainAbility/Index.ets`: ```typescript import napiModule from 'libnativeapi.so' // 编译生成的模块 // 调用C++函数add let result = napiModule.add(25, 17) ``` **步骤2:实现C++绑定** `src/main/cpp/native_api.cpp`核心代码: ```cpp #include <napi/native_api.h> // 实现加法函数 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); // 解析参数 int a, b; napi_get_value_int32(env, args[0], &a); napi_get_value_int32(env, args[1], &b); // 返回结果 napi_value sum; napi_create_int32(env, a + b, &sum); return sum; } // 模块导出 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, 1, &desc); return exports; } NAPI_MODULE(nativeapi, Init) // 模块名为nativeapi ``` #### 3. 编译配置 `CMakeLists.txt`关键配置: ```cmake find_package(NAPI REQUIRED) # 查找NAPI头文件 add_library(nativeapi SHARED native_api.cpp) target_link_libraries(nativeapi PUBLIC libnapi.so) ``` --- ### **三、内核移植中的NAPI兼容性关键点** 当开发板完成内核移植后,需确保: 1. **NDK工具链匹配** 验证开发板ABI(如`arm64-v8a`)与DevEco Studio中NDK版本一致: ```bash $ file ./build/default/intermediates/ndkBuild/release/obj/local/arm64-v8a/libnativeapi.so ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV) # 输出应为开发板架构 ``` 2. **内核符号表导出** NAPI模块可能依赖内核函数(如`openat`),需在内核编译配置中开启: ```makefile CONFIG_KALLSYMS=y # 允许动态加载模块访问内核符号 CONFIG_COMPAT=y # 兼容不同位宽的模块 ``` 3. **HDF驱动对接** 通过NAPI调用HDF驱动示例: ```cpp #include <hdf_base.h> #include <gpio_if.h> napi_value ReadGpio(napi_env env, napi_callback_info info) { uint16_t gpioId = 5; // GPIO引脚号 uint16_t val; int ret = GpioRead(gpioId, &val); // 调用HDF驱动接口 // ...返回结果到ArkTS } ``` --- ### **四、调试与性能优化** #### 1. 日志追踪 在C++代码中使用`hilog`输出: ```cpp #include <hilog/log.h> OH_LOG_Print(LOG_APP, LOG_INFO, 0xFF00, "NAPI", "Add function called"); ``` ArkTS侧过滤日志:`hdc_std shell "hilog -g napi"` #### 2. 内存泄漏检测 使用**NAPI内存钩子**: ```cpp napi_add_env_cleanup_hook(env, [](void* data) { // 检查未释放的napi_value }, nullptr); ``` #### 3. 性能瓶颈定位 - **ArkTS调用开销**:避免高频调用(>1000次/秒),改用批量处理 - **数据转换优化**:使用`napi_create_arraybuffer`代替`napi_create_array`传输二进制数据 --- ### **典型问题解决方案** | 问题现象 | 原因 | 解决措施 | |-----------------------------|-----------------------|------------------------------------------------| | `undefined symbol: napi_get_boolean` | NDK版本不匹配 | 在`CMakeLists.txt`中指定`target_link_libraries(... libnapi.so)`[^2] | | 调用NAPI导致系统重启 | 栈溢出 | 增大内核栈:`CONFIG_ARM64_STACK_SIZE=8192` | | HDF调用返回`-6`(权限不足) | SELinux策略限制 | 在`/etc/selinux/targeted/policy`添加`allow app domain:chr_file rw;`[^3] | --- ### **五、进阶:NAPI异步工作模型** 处理耗时操作防止阻塞JS线程: ```cpp struct AsyncWork { napi_async_work work; napi_deferred deferred; int result; }; // 在工作线程执行计算 void ExecuteWork(napi_env env, void* data) { AsyncWork* ctx = (AsyncWork*)data; ctx->result = HeavyCompute(); // 耗时计算 } // 返回结果到Promise void CompleteWork(napi_env env, napi_status status, void* data) { AsyncWork* ctx = (AsyncWork*)data; napi_resolve_deferred(env, ctx->deferred, ...); napi_delete_async_work(env, ctx->work); delete ctx; } napi_value AsyncCompute(napi_env env, napi_callback_info info) { // 创建异步上下文 AsyncWork* ctx = new AsyncWork; napi_create_promise(env, &ctx->deferred, &promise); // 创建异步任务 napi_create_async_work(env, nullptr, ..., ExecuteWork, CompleteWork, ctx, &ctx->work); napi_queue_async_work(env, ctx->work); return promise; } ``` ArkTS调用方式: ```typescript napiModule.asyncCompute().then((result) => { console.log("Result: " + result) }) ``` --- §§相关问题§§ 1. 如何确保NAPI模块在多线程环境下的线程安全? 2. NAPI与HDF驱动交互时如何进行高效数据传输? 3. 当开发板更换为RISC-V架构时,NAPI模块需要做哪些适配? 4. 如何调试NAPI模块中的内存泄漏问题? </think>### OpenHarmony应用开发与NAPI详解OpenHarmony开发板完成内核移植后,NAPI(Native API)是连接ArkTS应用与底层C/C++代码的核心桥梁。结合您关注的NAPI内容,以下是系统化开发指南: --- ### 一、NAPI的核心作用 NAPI允许ArkTS直接调用C/C++编写的本地模块,实现: 1. **高性能计算**:如音视频处理、图像渲染(比纯ArkTS快$3\times$以上)[^1] 2. **硬件操作**:直接访问GPIO、I2C等外设 3. **复用现有C/C++库**:如FFmpeg、OpenCV 4. **系统服务扩展**:创建自定义系统服务 --- ### 二、NAPI开发全流程(以GPIO控制为例) #### 1. 环境配置 在`build-profile.json5`中添加NDK依赖: ```json "externalNativeOptions": { "path": "./src/main/cpp/CMakeLists.txt" // 指向C++工程 } ``` #### 2. 创建NAPI模块 **ArkTS侧调用接口** (`entry/src/main/ets/MainAbility/Index.ets`): ```typescript import napiGpio from 'libgpio.so' // 编译后的NAPI模块 // 调用C++函数 let status = napiGpio.setGpioValue(5, 1); // 控制GPIO5输出高电平 ``` **C++侧实现** (`src/main/cpp/gpio_napi.cpp`): ```cpp #include <napi/native_api.h> #include "hdf_sbuf.h" #include "gpio_if.h" // HDF GPIO驱动头文件 // NAPI方法实现 napi_value SetGpioValue(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); // 解析ArkTS参数 int gpioNum, value; napi_get_value_int32(env, args[0], &gpioNum); napi_get_value_int32(env, args[1], &value); // 调用HDF驱动接口 struct GpioIoCmd ioCmd = {gpioNum, value, 0}; int ret = GpioIoWrite(gpioNum, &ioCmd); // 返回结果到ArkTS napi_value result; napi_create_int32(env, ret, &result); return result; } // 模块注册 napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc = { "setGpioValue", nullptr, SetGpioValue, nullptr, nullptr, nullptr, napi_default, nullptr }; napi_define_properties(env, exports, 1, &desc); return exports; } NAPI_MODULE(gpioapi, Init) // 模块名=gpioapi ``` #### 3. 编译配置 (`CMakeLists.txt`) ```cmake find_package(NAPI REQUIRED) # 查找NAPI头文件 add_library(gpioapi SHARED gpio_napi.cpp) target_link_libraries(gpioapi PUBLIC libnapi.so ${HDF_GPIO_LIB}) ``` --- ### 三、内核移植中的NAPI兼容性关键点 1. **HDF驱动支持验证** 确保内核编译时开启: ```makefile CONFIG_DRIVERS_HDF_GPIO=y # GPIO驱动支持 CONFIG_DRIVERS_HDF_PLATFORM=y ``` 检查`/vendor/[厂商]/hdf_config/device_info.hcs`包GPIO设备节点[^3]: ```hcs gpio_config { controller_0x120d0000 :: gpio_controller { match_attr = "gpio"; groupNum = 12; bitNum = 8; regBase = 0x120d0000; } } ``` 2. **NDK工具链匹配** - 开发板ABI(如`arm64-v8a`)必须与NDK版本一致 - 验证命令: ```bash $ readelf -h ./build/libs/arm64-v8a/libgpioapi.so Machine: AArch64 # 必须匹配开发板架构 ``` 3. **内核符号导出** 在`kernel/linux/config`中添加: ```makefile CONFIG_KALLSYMS=y # 允许动态模块访问内核符号 CONFIG_COMPAT=y # 兼容32/64位模块 ``` --- ### 四、调试与优化技巧 #### 1. 内存泄漏检测 使用NAPI内存钩子: ```cpp napi_add_env_cleanup_hook(env, [](void* data) { // 检查未释放的napi_value }, nullptr); ``` #### 2. 异步操作模型 避免阻塞ArkTS运行时: ```cpp napi_create_async_work(env, nullptr, ..., [](napi_env env, void* data) { /* 工作线程执行 */ }, [](napi_env env, napi_status status, void* data) { /* 回调主线程 */ } ); ``` #### 3. 性能瓶颈定位 - **ArkTS调用开销**:单次调用 >5μs(Hi3516DV300实测) - **数据传输优化**: ```cpp // 使用ArrayBuffer代替普通数组 napi_create_arraybuffer(env, bufferSize, &bufferData, &arrayBuffer); ``` --- ### 五、典型问题解决方案 | **问题现象** | **根本原因** | **解决方案** | |----------------------------|--------------------------|------------------------------------------| | `undefined symbol: napi_get_boolean` | NDK版本不匹配 | 在`CMakeLists.txt`中指定`target_link_libraries(... libnapi.so)`[^1] | | 调用导致系统重启 | 栈溢出 | 增大内核栈:`CONFIG_ARM64_STACK_SIZE=8192` | | HDF返回`-6`(权限不足) | SELinux策略限制 | 添加`/etc/selinux/targeted/policy/app.te`:<br>`allow app domain:chr_file rw;`[^3] | --- ### 六、进阶开发方向 1. **封装HDF驱动为NAPI**:将`//drivers/peripheral/gpio`驱动封装为JS可调用API 2. **混合编程模型**: ```mermaid graph LR A[ArkTS UI] --> B(NAPI异步调用) B --> C[C++计算引擎] C --> D[HDF驱动层] ``` 3. **跨进程通信**:通过NAPI访问`distributed_scheduler`服务实现设备协同 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值