• 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

    1. #ifndef NATIVETEST_TEST_C_H
    2. #define NATIVETEST_TEST_C_H
    3. class test_c {
    4. public:
    5. explicit test_c();
    6. ~test_c();
    7. int add(int a, int b);
    8. int sub(int a, int b);
    9. };
    10. #endif //NATIVETEST_TEST_C_H

    test_c.cpp

    1. #include "test_c.h"
    2. test_c::test_c() {}
    3. test_c::~test_c() {}
    4. int test_c::add(int a, int b) {
    5. return a + b;
    6. }
    7. int test_c::sub(int a, int b) {
    8. return a - b;
    9. }

    修改CMakeLists.txt文件

            前面都是建项目时默认写好的语句,主要加了一行add_library(test_c SHARED test_c.cpp)

    1. cmake_minimum_required(VERSION 3.5.0)
    2. #…………中间省略
    3. #第一个test_c是指将要生成的so文件名,最终的名称会变成libtest_c.so
    4. 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文件的名称自行修改

    1. # the minimum version of CMake.
    2. cmake_minimum_required(VERSION 3.5.0)
    3. #项目名称,创建项目时填的值
    4. project(NativeTest)
    5. set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
    6. if(DEFINED PACKAGE_FIND_FILE)
    7. include(${PACKAGE_FIND_FILE})
    8. endif()
    9. # 添加头文件.h目录,包括cpp,cpp/include,告诉cmake去这里找到代码引入的头文件
    10. # 一般头文件都放在cpp/include下
    11. include_directories(${NATIVERENDER_ROOT_PATH}
    12. ${NATIVERENDER_ROOT_PATH}/include)
    13. add_library(entry SHARED napi_init.cpp)
    14. target_link_libraries(entry PUBLIC libace_napi.z.so)
    15. #add_library(test_c SHARED test_c.cpp)
    16. #会根据不同的架构去不同的目录下找到对应的so文件
    17. target_link_libraries(entry PUBLIC ${NATIVERENDER_ROOT_PATH}/libs/${OHOS_ARCH}/libtest_c.so)

    调用so库中的方法

            以下代码可以复制进创建项目时生成的napi_init.cpp文件中

    1. #include "test_c.h"
    2. // 如果是用C编写的库,需要在extern "C"{}中包裹,否则会出现链接错误
    3. // extern "C"{
    4. // #include "test_c.h"
    5. // }
    6. #include //日志输出所需要的库
    7. #pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
    8. void test_demo()
    9. {
    10. // 这里只是演示调用引用的so库的方法
    11. test_c test;
    12. int r1 = test.add(10, 5);
    13. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
    14. int r2 = test.sub(10, 5);
    15. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
    16. }

    napi_init.cpp完整代码

    1. #include "napi/native_api.h"
    2. #include "test_c.h"
    3. // 如果是用C编写的库,需要在extern "C"{}中包裹,否则会出现链接错误
    4. // extern "C"{
    5. // #include "test_c.h"
    6. // }
    7. #include //日志输出所需要的库
    8. #pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
    9. void test_demo()
    10. {
    11. // 这里只是演示调用引用的so库的方法
    12. test_c test;
    13. int r1 = test.add(10, 5);
    14. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
    15. int r2 = test.sub(10, 5);
    16. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
    17. }
    18. static napi_value Add(napi_env env, napi_callback_info info)
    19. {
    20. test_demo();
    21. size_t argc = 2;
    22. napi_value args[2] = {nullptr};
    23. napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
    24. napi_valuetype valuetype0;
    25. napi_typeof(env, args[0], &valuetype0);
    26. napi_valuetype valuetype1;
    27. napi_typeof(env, args[1], &valuetype1);
    28. double value0;
    29. napi_get_value_double(env, args[0], &value0);
    30. double value1;
    31. napi_get_value_double(env, args[1], &value1);
    32. napi_value sum;
    33. napi_create_double(env, value0 + value1, &sum);
    34. return sum;
    35. }
    36. EXTERN_C_START
    37. static napi_value Init(napi_env env, napi_value exports)
    38. {
    39. napi_property_descriptor desc[] = {
    40. { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }
    41. };
    42. napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    43. return exports;
    44. }
    45. EXTERN_C_END
    46. static napi_module demoModule = {
    47. .nm_version = 1,
    48. .nm_flags = 0,
    49. .nm_filename = nullptr,
    50. .nm_register_func = Init,
    51. .nm_modname = "entry",
    52. .nm_priv = ((void*)0),
    53. .reserved = { 0 },
    54. };
    55. extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
    56. {
    57. napi_module_register(&demoModule);
    58. }

    使用Node-API实现ArkTS与C/C++模块之间的交互

    定义接口

            在index.d.ts文件中,提供ArkTS/JS侧的接口方法。

    1. //一个同步返回的加法计算
    2. export const add: (a: number, b: number) => number;
    3. //一个异步返回的加法计算
    4. export const addWithCallBack: (a: number, b: number, callBack: (result:number) => void) => number;
    5. //各种数据类型的参数传参demo,这里只列了几个常见的
    6. export const paramsTest: (a: number, b: string, c:boolean, d:string[], e:ArrayBuffer) => void;

    接口实现

            在index.d.ts定义的接口需在napi_init.cpp有对应实现。

    1. #include "napi/native_api.h"
    2. #include "test_c.h"
    3. #include
    4. #include //日志输出所需要的库
    5. #pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
    6. void test_demo()
    7. {
    8. // 这里只是演示调用引用的so库的方法
    9. test_c test;
    10. int r1 = test.add(10, 5);
    11. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
    12. int r2 = test.sub(10, 5);
    13. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
    14. }
    15. //一个同步返回的加法计算
    16. static napi_value Add(napi_env env, napi_callback_info info)
    17. {
    18. test_demo();//测试调用so库的方法
    19. size_t argc = 2; // 参数个数
    20. napi_value args[2] = {nullptr};
    21. napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
    22. // 获取第一个参数
    23. double value0;
    24. napi_get_value_double(env, args[0], &value0);
    25. // 获取第二个参数
    26. double value1;
    27. napi_get_value_double(env, args[1], &value1);
    28. // 返回值
    29. napi_value sum;
    30. napi_create_double(env, value0 + value1, &sum);
    31. return sum;
    32. }
    33. //定义需要传递给异步工作的数据结构
    34. struct CallbackContext {
    35. napi_env env = nullptr;
    36. napi_ref recvCallbackRef = nullptr;
    37. napi_async_work work;
    38. //需要传入的参数
    39. double a;
    40. double b;
    41. //返回的参数
    42. double result;
    43. };
    44. // 这里可以进行耗时操作, 方法名可修改,但参数固定
    45. void AddAsync(napi_env env, void *data) {
    46. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddAsync is called");
    47. // 获取传入的参数
    48. CallbackContext *context = (CallbackContext *)data;
    49. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "context.a: %{public}f", context->a);
    50. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "context.b: %{public}f", context->b);
    51. // 模拟耗时操作
    52. std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 睡眠1秒
    53. // 计算结果
    54. context->result = context->a + context->b;
    55. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddAsync end");
    56. }
    57. //AddAsync执行完毕后会自动调用这个方法, 方法名可修改,但参数固定
    58. void AddCallBack(napi_env env, napi_status status, void *data) {
    59. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddCallBack is called");
    60. CallbackContext *context = (CallbackContext *)data;
    61. napi_value recvCallback = nullptr;
    62. napi_get_reference_value(context->env, context->recvCallbackRef, &recvCallback);
    63. // 因为回调方法只有一个参数, 若有多个参数要给每个参数都赋值
    64. int size = 1;
    65. napi_value argv[size];
    66. napi_create_double(env, context->result, &argv[0]);
    67. napi_value ret;
    68. napi_call_function(env, nullptr, recvCallback, size, argv, &ret);
    69. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddCallBack delete");
    70. napi_delete_reference(context->env, context->recvCallbackRef);
    71. napi_delete_async_work(context->env, context->work);
    72. delete context;
    73. }
    74. //一个异步返回的加法计算
    75. static napi_value AddWithCallBack(napi_env env, napi_callback_info info)
    76. {
    77. size_t argc = 3;
    78. napi_value args[3] = {nullptr};
    79. napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
    80. double value0;
    81. napi_get_value_double(env, args[0], &value0);
    82. double value1;
    83. napi_get_value_double(env, args[1], &value1);
    84. // 获取回调函数
    85. napi_ref recvCallbackRef;
    86. napi_create_reference(env, args[2], 1, &recvCallbackRef);
    87. CallbackContext *context = new CallbackContext;
    88. context->env = env;
    89. context->recvCallbackRef = recvCallbackRef;
    90. context->a = value0;
    91. context->b = value1;
    92. //异步调用
    93. napi_value resource;
    94. //第二个参数为当前方法名
    95. napi_create_string_latin1(context->env, "AddWithCallBack", NAPI_AUTO_LENGTH, &resource);
    96. //创建异步工作,AddAsync为耗时操作的方法,AddCallBack为耗时操作完成后的回调方法,可替换成自己实际所需的方法
    97. napi_create_async_work(context->env, nullptr, resource, AddAsync, AddCallBack, context,
    98. &context->work);
    99. napi_queue_async_work(context->env, context->work); // 实现在UI主线程调用
    100. // 直接返回空值,实际返回值通过回调方法返回
    101. napi_value result = nullptr;
    102. napi_get_undefined(env, &result);
    103. return result;
    104. }
    105. static napi_value ParamsTest(napi_env env, napi_callback_info info)
    106. {
    107. size_t argc = 5;
    108. napi_value args[5] = {nullptr};
    109. napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
    110. //获取第一个参数--数字类型
    111. double a;
    112. napi_get_value_double(env, args[0], &a);
    113. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "double a: %{public}f", a);
    114. //获取第二个参数--字符串类型
    115. size_t typeLen = 0;
    116. napi_get_value_string_utf8(env, args[1], nullptr, 0, &typeLen);
    117. char *b = new char[typeLen + 1];
    118. napi_get_value_string_utf8(env, args[0], b, typeLen + 1, &typeLen);
    119. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "string b: %{public}s", b);
    120. //获取第三个参数--bool类型
    121. bool c;
    122. napi_get_value_bool(env, args[2], &c);
    123. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "boolean c: %{public}d", c);
    124. //第四个参数--数组类型
    125. // 检查参数是否为数组
    126. bool is_array;
    127. napi_is_array(env, args[3], &is_array);
    128. if (!is_array) {
    129. napi_throw_type_error(env, nullptr, "Argument must be an array");
    130. return nullptr;
    131. }
    132. // 获取数组长度
    133. uint32_t length;
    134. napi_get_array_length(env, args[3], &length);
    135. // 遍历数组
    136. for (int i = 0; i < length; i++) {
    137. napi_value result;
    138. napi_get_element(env, args[3], i, &result);
    139. size_t len = 0;
    140. napi_get_value_string_utf8(env, result, nullptr, 0, &len);
    141. char *text = new char[len + 1];
    142. napi_get_value_string_utf8(env, result, text, len + 1, &len);
    143. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "array d[%{public}d]: %{public}s", i, text);
    144. }
    145. //获取第五个参数--ArrayBuffer类型
    146. bool is_array_buffer;
    147. // 检查第五个参数是否为ArrayBuffer
    148. napi_is_arraybuffer(env, args[4], &is_array_buffer);
    149. if (is_array_buffer) {
    150. napi_value array_buffer_value;
    151. uint8_t *data;
    152. size_t byte_length;
    153. array_buffer_value = args[4];
    154. // 获取ArrayBuffer的外部数据指针和长度
    155. napi_get_arraybuffer_info(env, array_buffer_value, (void **)&data, &byte_length);
    156. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "sizeof(uint8_t) = %{public}d", (int)sizeof(uint8_t));
    157. // 使用data指针处理ArrayBuffer数据
    158. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "arraybuffer size = %{public}d, (ie: bytes=%{public}d) ",
    159. (int)(byte_length / sizeof(uint8_t)), (int)byte_length);
    160. // for (int i = 0; i < ((int)(byte_length / sizeof(uint8_t))); ++i) {
    161. // OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "data[%{public}d] = %{public}d ", i, *(data + i));
    162. // }
    163. } else {
    164. // 参数不是ArrayBuffer的处理逻辑
    165. OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "e is not ArrayBuffer");
    166. }
    167. napi_value result = nullptr;
    168. napi_get_undefined(env, &result);
    169. return result;
    170. }
    171. EXTERN_C_START
    172. static napi_value Init(napi_env env, napi_value exports)
    173. {
    174. napi_property_descriptor desc[] = {
    175. //第一个参数是index.d.ts定义的方法名,第三个参数是当前cpp文件中的方法名
    176. { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
    177. { "addWithCallBack", nullptr, AddWithCallBack, nullptr, nullptr, nullptr, napi_default, nullptr },
    178. { "paramsTest", nullptr, ParamsTest, nullptr, nullptr, nullptr, napi_default, nullptr }
    179. };
    180. napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    181. return exports;
    182. }
    183. EXTERN_C_END
    184. static napi_module demoModule = {
    185. .nm_version = 1,
    186. .nm_flags = 0,
    187. .nm_filename = nullptr,
    188. .nm_register_func = Init,
    189. .nm_modname = "entry",
    190. .nm_priv = ((void*)0),
    191. .reserved = { 0 },
    192. };
    193. extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
    194. {
    195. napi_module_register(&demoModule);
    196. }

    ArtTS侧调用接口

    直接修改默认创建的Index.ets

    1. import { hilog } from '@kit.PerformanceAnalysisKit';
    2. import testNapi from 'libentry.so';//导入C++模块
    3. @Entry
    4. @Component
    5. struct Index {
    6. callBack = (result:number)=>{
    7. hilog.info(0x0000, 'testTag', 'addWithCallBack(4,5) = %{public}d', result);
    8. }
    9. build() {
    10. Row() {
    11. Column({space:10}) {
    12. Button('add(2,3)')
    13. .onClick(()=>{
    14. hilog.info(0x0000, 'testTag', 'add(2,3) = %{public}d', testNapi.add(2, 3));
    15. })
    16. Button('addWithCallBack(4,5)')
    17. .onClick(()=>{
    18. hilog.info(0x0000, 'testTag', 'addWithCallBack(4,5) Begin');
    19. testNapi.addWithCallBack(4,5,this.callBack)
    20. })
    21. Button('paramsTest')
    22. .onClick(()=>{
    23. //获取一个arraybuffer数据,本地有什么图片资源就用哪个就行
    24. getContext().resourceManager.getMediaContent($r('app.media.app_icon')).then((mediaContent)=>{
    25. hilog.info(0x0000, 'testTag', 'paramsTest getMediaContent success');
    26. hilog.info(0x0000, 'testTag', 'paramsTest Begin');
    27. testNapi.paramsTest(4, "text", false, ['apple','boy','cat'],mediaContent.buffer)
    28. })
    29. })
    30. }
    31. .width('100%')
    32. }
    33. .height('100%')
    34. }
    35. }

  • 相关阅读:
    【前端学习 - Vue (10) Vue 中的 key 有什么作用?】
    通信总线协议三 :IIC
    一文读懂机智云物联网APP开发
    css案例14——文字渐变
    【GIT】常用操作总结
    Qt5开发从入门到精通——第七篇一节( 图形视图——动画效果 )
    常见聚类算法及使用--层次聚类(Agglomerative clustering)
    Mybatis和MybatisPlus:数据库操作工具的对比
    Spring Cloud Alibaba微服务第16章之服务容错
    分布式机器学习:逻辑回归的并行化实现(PySpark)
  • 原文地址:https://blog.csdn.net/m940034240/article/details/142177999