HarmonyOS Next鸿蒙NDK使用示例

news/2024/12/22 10:08:22/

创建一个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_Hclass 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_ENDstatic 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;// 检查第五个参数是否为ArrayBuffernapi_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_ENDstatic 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%')}
}


http://www.ppmy.cn/news/1526568.html

相关文章

C#笔记8 线程是什么?多线程怎么实现和操作?

这和前面的学习内容可能有点不太连贯&#xff0c;但是呢我们一般来说的学习就是遇到什么困难就去学习什么&#xff0c;这也是为什么看那些循序渐进的教程虽然学的很饱满&#xff0c;但是我们有时会学了前面忘记了后面&#xff0c;或者对某个板块理解不深&#xff0c;乃至于写代…

Ready Go

本文首发在这里 温馨提示 XX年&#xff0c;指的是20XX年&#xff0c;后跟以前、以后之类&#xff0c;均包含本数链接较多&#xff0c;只是想言之有物&#xff0c;已拒绝相同外链&#xff0c;仅看关心的即可已尽量只引用自己的东西&#xff0c;16年后仓库(11/13)&#xff0c;2…

floodfill+DFS(1)

文章目录 图像渲染岛屿数量岛屿的最大面积被围绕的岛屿 图像渲染 class Solution { public:int m 0, n 0;bool check[51][51] {false};vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {m image.size…

spark sql详解

Spark SQL 是 Apache Spark 的一个核心模块&#xff0c;专门用于处理结构化数据。它不仅支持传统的 SQL 查询&#xff0c;还支持复杂的分析和计算功能&#xff0c;利用分布式计算平台的能力来高效处理大规模数据。以下是对 Spark SQL 的详细解析&#xff0c;涵盖其架构、工作原…

C++学习笔记----7、使用类与对象获得高性能(一)---- 书写类(2)

2.2、定义成员函数 前面对SpreadsheetCell类的定义足以让你生成类的对象。然而&#xff0c;如果想调用setValue()或者getValue()成员函数&#xff0c;连接器就会抱怨这些函数没有定义。这是因为到目前为止&#xff0c;这些成员函数只有原型&#xff0c;而还没有实现。通常&…

根据NVeloDocx Word模板引擎生成Word(三)

基于永久免费开放的《E6低代码开发平台》的Word模版引擎NVeloDocx&#xff0c;实现根据Word模版生成Word文件&#xff0c;前面2篇已经非常详细介绍了《主表单字段》&#xff0c;《子表记录循环输入到表格》。那这一篇我们就介绍插入单张图片、二维码&#xff0c;条形码等等&…

Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化

Android Tools | 如何使用Draw.io助力Android开发&#xff1a;从UI设计到流程优化 1. 引言 在Android开发中&#xff0c;视觉化设计与流程管理至关重要。虽然开发工具如Android Studio强大&#xff0c;但它并不适用于所有设计场景。Draw.io是一款免费的在线绘图工具&#xff…

opencv之傅里叶变换

文章目录 前言理论基础Numpy实现傅里叶变换实现傅里叶变换实现逆傅里叶变换 高通滤波示例OpenCV实现傅里叶变换实现傅里叶变换实现逆傅里叶变换 低通滤波示例 前言 图像处理一般分为空间域处理和频率域处理。 空间域处理是直接对图像内的像素进行处理。空间域处理主要划分为灰…