HarmonyOS Next鸿蒙NDK使用示例
创建一个Native C++项目
跟普通项目相比,主要区别是多了一个cpp文件夹、oh-package.json5中的dependencies引入还有build-profile.json5中的externalNativeOptions配置,abiFilters是支持的CPU架构,目前移动端项目只支持arm64-v8a、x86_64两种。
普通项目也可以复制以下文件/配置到对应位置变成一个Native C++项目。但要注意把一些entry的字符替换成你的module名称。如果同样是entry就不用修改。
生成一个测试用的so库
编写测试代码
新建两个文件test_c.cpp和test_c.h,位置如下
test_c.h
#ifndef NATIVETEST_TEST_C_H
#define NATIVETEST_TEST_C_H
class test_c {
public:
explicit test_c();
~test_c();
int add(int a, int b);
int sub(int a, int b);
};
#endif //NATIVETEST_TEST_C_H
test_c.cpp
#include "test_c.h"
test_c::test_c() {}
test_c::~test_c() {}
int test_c::add(int a, int b) {
return a + b;
}
int test_c::sub(int a, int b) {
return a - b;
}
修改CMakeLists.txt文件
前面都是建项目时默认写好的语句,主要加了一行add_library(test_c SHARED test_c.cpp)
cmake_minimum_required(VERSION 3.5.0)
#…………中间省略
#第一个test_c是指将要生成的so文件名,最终的名称会变成libtest_c.so
add_library(test_c SHARED test_c.cpp)
构建so库
点击Build -> Build Hap(s)/APP(s) -> Build Hap(s), 生成的so文件在以下目录。生成后就可以把test_c.cpp删掉了。
引用so库
复制so文件到对应目录
在cpp目录下建一个libs文件夹,按照CPU架构把so文件放到对应的文件夹中(同时把所有.h头文件放到include文件夹中,这里因为在上个步骤中已经写好了test_c.h,所以只需放入so文件)
修改CMakeLists.txt文件
主要是加了最后一行target_link_libraries(entry PUBLIC ${NATIVERENDER_ROOT_PATH}/libs/${OHOS_ARCH}/xxxxx.so),具体so文件的名称自行修改
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
#项目名称,创建项目时填的值
project(NativeTest)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
# 添加头文件.h目录,包括cpp,cpp/include,告诉cmake去这里找到代码引入的头文件
# 一般头文件都放在cpp/include下
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
#add_library(test_c SHARED test_c.cpp)
#会根据不同的架构去不同的目录下找到对应的so文件
target_link_libraries(entry PUBLIC ${NATIVERENDER_ROOT_PATH}/libs/${OHOS_ARCH}/libtest_c.so)
调用so库中的方法
以下代码可以复制进创建项目时生成的napi_init.cpp文件中
#include "test_c.h"
// 如果是用C编写的库,需要在extern "C"{}中包裹,否则会出现链接错误
// extern "C"{
// #include "test_c.h"
// }
#include <hilog/log.h>//日志输出所需要的库
#pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
void test_demo()
{
// 这里只是演示调用引用的so库的方法
test_c test;
int r1 = test.add(10, 5);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
int r2 = test.sub(10, 5);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
}
napi_init.cpp完整代码
#include "napi/native_api.h"
#include "test_c.h"
// 如果是用C编写的库,需要在extern "C"{}中包裹,否则会出现链接错误
// extern "C"{
// #include "test_c.h"
// }
#include <hilog/log.h>//日志输出所需要的库
#pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
void test_demo()
{
// 这里只是演示调用引用的so库的方法
test_c test;
int r1 = test.add(10, 5);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
int r2 = test.sub(10, 5);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
}
static napi_value Add(napi_env env, napi_callback_info info)
{
test_demo();
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);
}
使用Node-API实现ArkTS与C/C++模块之间的交互
定义接口
在index.d.ts文件中,提供ArkTS/JS侧的接口方法。
//一个同步返回的加法计算
export const add: (a: number, b: number) => number;
//一个异步返回的加法计算
export const addWithCallBack: (a: number, b: number, callBack: (result:number) => void) => number;
//各种数据类型的参数传参demo,这里只列了几个常见的
export const paramsTest: (a: number, b: string, c:boolean, d:string[], e:ArrayBuffer) => void;
接口实现
在index.d.ts定义的接口需在napi_init.cpp有对应实现。
#include "napi/native_api.h"
#include "test_c.h"
#include <thread>
#include <hilog/log.h>//日志输出所需要的库
#pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
void test_demo()
{
// 这里只是演示调用引用的so库的方法
test_c test;
int r1 = test.add(10, 5);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
int r2 = test.sub(10, 5);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
}
//一个同步返回的加法计算
static napi_value Add(napi_env env, napi_callback_info info)
{
test_demo();//测试调用so库的方法
size_t argc = 2; // 参数个数
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
// 获取第一个参数
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;
}
//定义需要传递给异步工作的数据结构
struct CallbackContext {
napi_env env = nullptr;
napi_ref recvCallbackRef = nullptr;
napi_async_work work;
//需要传入的参数
double a;
double b;
//返回的参数
double result;
};
// 这里可以进行耗时操作, 方法名可修改,但参数固定
void AddAsync(napi_env env, void *data) {
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddAsync is called");
// 获取传入的参数
CallbackContext *context = (CallbackContext *)data;
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "context.a: %{public}f", context->a);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "context.b: %{public}f", context->b);
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 睡眠1秒
// 计算结果
context->result = context->a + context->b;
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddAsync end");
}
//AddAsync执行完毕后会自动调用这个方法, 方法名可修改,但参数固定
void AddCallBack(napi_env env, napi_status status, void *data) {
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddCallBack is called");
CallbackContext *context = (CallbackContext *)data;
napi_value recvCallback = nullptr;
napi_get_reference_value(context->env, context->recvCallbackRef, &recvCallback);
// 因为回调方法只有一个参数, 若有多个参数要给每个参数都赋值
int size = 1;
napi_value argv[size];
napi_create_double(env, context->result, &argv[0]);
napi_value ret;
napi_call_function(env, nullptr, recvCallback, size, argv, &ret);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddCallBack delete");
napi_delete_reference(context->env, context->recvCallbackRef);
napi_delete_async_work(context->env, context->work);
delete context;
}
//一个异步返回的加法计算
static napi_value AddWithCallBack(napi_env env, napi_callback_info info)
{
size_t argc = 3;
napi_value args[3] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
// 获取回调函数
napi_ref recvCallbackRef;
napi_create_reference(env, args[2], 1, &recvCallbackRef);
CallbackContext *context = new CallbackContext;
context->env = env;
context->recvCallbackRef = recvCallbackRef;
context->a = value0;
context->b = value1;
//异步调用
napi_value resource;
//第二个参数为当前方法名
napi_create_string_latin1(context->env, "AddWithCallBack", NAPI_AUTO_LENGTH, &resource);
//创建异步工作,AddAsync为耗时操作的方法,AddCallBack为耗时操作完成后的回调方法,可替换成自己实际所需的方法
napi_create_async_work(context->env, nullptr, resource, AddAsync, AddCallBack, context,
&context->work);
napi_queue_async_work(context->env, context->work); // 实现在UI主线程调用
// 直接返回空值,实际返回值通过回调方法返回
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
static napi_value ParamsTest(napi_env env, napi_callback_info info)
{
size_t argc = 5;
napi_value args[5] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
//获取第一个参数--数字类型
double a;
napi_get_value_double(env, args[0], &a);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "double a: %{public}f", a);
//获取第二个参数--字符串类型
size_t typeLen = 0;
napi_get_value_string_utf8(env, args[1], nullptr, 0, &typeLen);
char *b = new char[typeLen + 1];
napi_get_value_string_utf8(env, args[0], b, typeLen + 1, &typeLen);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "string b: %{public}s", b);
//获取第三个参数--bool类型
bool c;
napi_get_value_bool(env, args[2], &c);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "boolean c: %{public}d", c);
//第四个参数--数组类型
// 检查参数是否为数组
bool is_array;
napi_is_array(env, args[3], &is_array);
if (!is_array) {
napi_throw_type_error(env, nullptr, "Argument must be an array");
return nullptr;
}
// 获取数组长度
uint32_t length;
napi_get_array_length(env, args[3], &length);
// 遍历数组
for (int i = 0; i < length; i++) {
napi_value result;
napi_get_element(env, args[3], i, &result);
size_t len = 0;
napi_get_value_string_utf8(env, result, nullptr, 0, &len);
char *text = new char[len + 1];
napi_get_value_string_utf8(env, result, text, len + 1, &len);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "array d[%{public}d]: %{public}s", i, text);
}
//获取第五个参数--ArrayBuffer类型
bool is_array_buffer;
// 检查第五个参数是否为ArrayBuffer
napi_is_arraybuffer(env, args[4], &is_array_buffer);
if (is_array_buffer) {
napi_value array_buffer_value;
uint8_t *data;
size_t byte_length;
array_buffer_value = args[4];
// 获取ArrayBuffer的外部数据指针和长度
napi_get_arraybuffer_info(env, array_buffer_value, (void **)&data, &byte_length);
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "sizeof(uint8_t) = %{public}d", (int)sizeof(uint8_t));
// 使用data指针处理ArrayBuffer数据
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "arraybuffer size = %{public}d, (ie: bytes=%{public}d) ",
(int)(byte_length / sizeof(uint8_t)), (int)byte_length);
// for (int i = 0; i < ((int)(byte_length / sizeof(uint8_t))); ++i) {
// OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "data[%{public}d] = %{public}d ", i, *(data + i));
// }
} else {
// 参数不是ArrayBuffer的处理逻辑
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "e is not ArrayBuffer");
}
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
//第一个参数是index.d.ts定义的方法名,第三个参数是当前cpp文件中的方法名
{ "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "addWithCallBack", nullptr, AddWithCallBack, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "paramsTest", nullptr, ParamsTest, 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);
}
ArtTS侧调用接口
直接修改默认创建的Index.ets
import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';//导入C++模块
@Entry
@Component
struct Index {
callBack = (result:number)=>{
hilog.info(0x0000, 'testTag', 'addWithCallBack(4,5) = %{public}d', result);
}
build() {
Row() {
Column({space:10}) {
Button('add(2,3)')
.onClick(()=>{
hilog.info(0x0000, 'testTag', 'add(2,3) = %{public}d', testNapi.add(2, 3));
})
Button('addWithCallBack(4,5)')
.onClick(()=>{
hilog.info(0x0000, 'testTag', 'addWithCallBack(4,5) Begin');
testNapi.addWithCallBack(4,5,this.callBack)
})
Button('paramsTest')
.onClick(()=>{
//获取一个arraybuffer数据,本地有什么图片资源就用哪个就行
getContext().resourceManager.getMediaContent($r('app.media.app_icon')).then((mediaContent)=>{
hilog.info(0x0000, 'testTag', 'paramsTest getMediaContent success');
hilog.info(0x0000, 'testTag', 'paramsTest Begin');
testNapi.paramsTest(4, "text", false, ['apple','boy','cat'],mediaContent.buffer)
})
})
}
.width('100%')
}
.height('100%')
}
}