【分享】HarmonyOS鸿蒙Next NAPI入门
【分享】HarmonyOS鸿蒙Next NAPI入门
背景
公司内部已经有现成的MQTT动态库,想在HarmonyOS平台上共享使用。查找官方指导后,发现可以通过NAPI方式,将MQTT C++库导入进来,然后封装一层ArkTS接口就可直接使用。
本篇内容是在按照官方指导下,自己做的一些调研性质的实践。
阅读完成后
将学会如何在已有的HarmonyOS应用工程中完成C++文件的添加以及TS与C++的交互
希望大家不用碰到类似的项目需求
NAPI简介
NAPI(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架
在HarmonyOS中,C API中的N-API接口可以实现ArkTS/TS/JS与C/C++之间的交互。N-API提供的接口名与三方Node.js一致,目前支持部分接口。
关于详细HarmonyOS 中的N-API开发教程,详见官方指导
关于Node.js的Node-API开发教程, https://nodejs.cn/api/n-api.html
环境条件
- DevEco Studio 3.1.1 Release
- HarmonyOS SDK API 9
效果图
功能演示
- ets文件获取C++生成的JSON对象
- ets文件获取C++生成的数组
- 引入第三方源码,并且使用其API【这里引用的国密算法SM4的实现-https://github.com/tonyonce2017/SM4】
- ets获取C++的运算结果
- ets实现回调方法, C++触发ets回调方法
实践步骤
备注:以添加 源码 形式演示
简易目录结构
粉色为添加和改动部分
- entry模块添加文件
- src/main 目录下创建“cpp”文件夹【名字必须为cpp】
- src/main 目录下创建主入口文件"main.cpp"【名字可以随意命名】
- src/main 目录下创建"CMakeLists.txt"文件【内容见 “片段1”】
片段1
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(HarmonyLearn)
# 设置源码文件目录
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 设置 .h 文件目录
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
# 设置自己的动态名称 和 对应的编译文件
add_library(customnapi SHARED main.cpp
sm4.cpp
util.cpp)
# 设置链接自己的动态库与其它动态库
target_link_libraries(customnapi PUBLIC libace_napi.z.so)
配置entry模块
entry模块中的build-profile.json5文件增加native选项【内容见 “片段2”】
片段2
...
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
},
...
}
...
ets引用so
在需要使用到"customnapi"库的文件中,通过import方式来进行引用
注意 虽然我们自己定义的库名称为"customnapi", 但是引用的时候应该为"libcustomnapi.so", 注意是以"lib"为前缀
import customnapi from 'libcustomnapi.so'
...
customnapi.passStr(...)
...
经过这三步,即可完成C++源码在HarmonyOS应用工程中的添加使用
示例解读
main.cpp
这个文件在本示例中用来注册C++源码模块使用的,文件名称可任意修改,因为其最终会被CMakeLists.txt文件引用,所以其文件名称无关紧要
这个文件主要分为四部分
- so api注册
- so模块说明
- napi接口描述
- C++函数定义
so注册,即在ets文件被加载时,触发import动作,然后会通过系统机制自动执行 napi_module_register 代码
so模块说明,即对so 模块的描述,包含NAPI接口描述入口,so模块名称,版本等
so api注册, 即ets函数名称和NAPI 函数名称的映射关系。比如,ets中调用add,因为有这里的映射,所以执行的是C++文件中的Add函数。
C++函数定义,即so库对外暴露的接口实现,比如 static napi_value Add(napi_env env, napi_callback_info info)
C++解析TS方法的参数
例如:Add函数
// 期望从ArkTS侧获取的参数的数量,napi_value可理解为ArkTS value在native方法中的表现形式。
//TS函数有2个参数
size_t argc = 2;
//创建一个包含2个元素的TS参数值数组
napi_value args[2] = {nullptr};
// 从info中,拿到从ArkTS侧传递过来的参数,此处获取了两个ArkTS参数,即arg[0]和arg[1]。
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
// 将获取的ArkTS参数转换为native信息,此处ArkTS侧传入了两个number,这里将其转换为native侧可以操作的double类型。
//解析TS函数中的第一个参数
double value0;
//声明于 js_native_api.h 文件,属于HarmonyOS SDK里边的一部分
napi_get_value_double(env, args[0], &value0);
//解析TS函数中的第二个参数
double value1;
//声明于 js_native_api.h 文件,属于HarmonyOS SDK里边的一部分
napi_get_value_double(env, args[1], &value1);
C++获取TS方法中的字符串类型参数长度
例如:passStr函数
static napi_value passStr(napi_env env, napi_callback_info info) {
//TS函数有1一个参数
size_t argc = 1;
//创建一个包含1个元素的TS参数值数组
napi_value args[1] = {nullptr};
// 从info中,拿到从ArkTS侧传递过来的参数,此处获取了一个ArkTS参数,即arg[0]。
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
//获取字符串参数的长度
size_t typeLen = 0;
napi_get_value_string_utf8(env, args[0], nullptr, 0, &typeLen);
//创建存储字符串的数组,数组大小为“字符串长度+1", 1代表字符串结束符
char *extContent = new char[typeLen + 1];
//解析TS的字符串参数,将内容赋值到char数组中
napi_get_value_string_utf8(env, args[0], extContent, typeLen + 1, &typeLen);
napi_value result;
//将字符串赋值给napi_value
napi_create_string_utf8(env, extContent, typeLen + 1, &result);
//释放数组空间
delete[] extContent;
//返回结果
return result;
}
C++返回数组类型
例如:generatorMockArray函数
static napi_value generatorMockArray(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);
//解析TS参数值,这里的值代表需要初始化的数组容量
int result;
napi_get_value_int32(env, args[0], &result);
napi_value ret;
// 创建返回类型为数组的对象
napi_create_array(env, &ret);
const int arrayLength = 4;
const char *target[arrayLength] = {"Blue", "Red", "Orange", "Yellow"};
// 根据TS入参设置数组的初始值
for (int i = 0; i < result && i < arrayLength; i++) {
napi_value tempValue;
//创建字符串类型的tempValue对象,把字符串赋值给tempValue
napi_create_string_utf8(env, target[i], strlen(target[i]),&tempValue);
// 数组中添加元素
napi_set_element(env, ret, i, tempValue);
}
return ret;
}
C++返回Object类型对象
例如:getCustomObject函数
/**
* 获取mock对象
* 格式
* {
* "testNumber" : 123,
* "testString" : "welcome",
* "testNested" : { "child" : "welcome"}
* }
*
* @param env
* @param info
* @return
*/
static napi_value getCustomObject(napi_env env, napi_callback_info info) {
//创建一个数字类型的对象: value_number
napi_value value_number;
napi_create_int32(env, 123, &value_number);
//创建一个字符串类型的对象: value_string
char *contentStr = "welcome";
napi_value value_string;
napi_create_string_utf8(env, contentStr, strlen(contentStr), &value_string);
//创建一个Object类型的对象:childObj
napi_value childObj;
napi_create_object(env, &childObj);
//childObj对象添加一个名为“child”的属性,其值为value_string
napi_set_named_property(env, childObj, "child", value_string);
//创建一个返回类型为Object的对象: obj
napi_value obj;
napi_create_object(env, &obj);
//obj对象添加一个名为“testNumber”的属性,其值为value_number
napi_set_named_property(env, obj, "testNumber", value_number);
//obj对象添加一个名为“testString”的属性,其值为value_string
napi_set_named_property(env, obj, "testString", value_string);
//obj对象添加一个名为“testNested”的属性,其值为childObj
napi_set_named_property(env, obj, "testNested", childObj);
return obj;
}
CMakeLists.txt
注释来源:https://www.51cto.com/article/751095.html
Cmake教程:https://cmake.org/cmake/help/latest/guide/tutorial/index.html
# cmake 最小版本
cmake_minimum_required(VERSION 3.4.1)
# 工程名称:HarmonyLearn
project(HarmonyLearn)
# set命令,格式为 set(key value),表示设置key的值为value,其中value可以是路径,也可以是许多文件。
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 添加项目编译所需要的头文件的目录
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
# 生成目标库文件customnapi.so,customnapi表示最终的库名称,SHARED表示生成的是动态链接库,
# main.cpp, sm4.cpp, util.cpp 表示最终生成的libcustomnapi.so中所包含的源码
# 如果要生成静态链接库,把SHARED该成STATIC即可
add_library(customnapi SHARED main.cpp
sm4.cpp
util.cpp)
# 把libcustomnapi.so链接到libace_napi.z.so上
target_link_libraries(customnapi PUBLIC libace_napi.z.so)
结尾
如果平时不太写C/C++的情况下,上手写NAPI,还是比较困难的,相当于HarmonyOS新手不知道如何展示一个文字一样。
这种开发模式在大多数公司都用不到,即使用到了,也不会采用全量源码编译接入方式,所以稍微看看就可以了。
祝好运!
示例代码:https://gitee.com/harveyblack/harmony-learn
更多关于【分享】HarmonyOS鸿蒙Next NAPI入门的实战教程也可以访问 https://www.itying.com/category-93-b0.html
多谢分享,很受用,公司之前主阵地就是C端,之前安卓版也研究了如何打通安卓到C端,现在刚换到鸿蒙这边,这篇文章真是让我受益匪浅。
更多关于【分享】HarmonyOS鸿蒙Next NAPI入门的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
有帮助就好,
如果是Native call js的话有啥好的实践方式么
https://gitee.com/harveyblack/harmony-learn 示例代码提示没有访问权限
有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html
对啊,没有权限,如何获取权限,
感谢分享,正准备研究下NAPI开发,非常有参考意义。
感谢分享,NAPI 在鸿蒙 native 库开发的应用网上的资料是相当稀少。
您有无关注 NAPI 和 TS 层互调的的性能折损问题呢?根据 NodeJS 的经验,处理文本涉及编码转换的,抑或传递大结构体等,效率都不高。您有最佳实践,抑或个人心得可以分享吗?盼赐教。
抱歉,我不太关心NAPI和JNI的性能问题,没法分享NAPI性能提升的问题。
HarmonyOS鸿蒙Next NAPI(Native API)是华为为开发者提供的原生接口,允许在鸿蒙系统中使用C/C++进行高性能开发。NAPI支持跨平台开发,适用于智能设备、手机、平板等多种终端。入门步骤包括:
- 安装DevEco Studio开发工具;
- 创建NAPI项目;
- 编写C/C++代码并编译;
- 在鸿蒙设备上调试和运行。
NAPI的优势在于其高效性和灵活性,适合需要直接操作硬件的应用场景。