【鸿蒙HarmonyOS Next实战开发】实现ArkTS/JS和C/C++的交互-Node-API ...

打印 上一主题 下一主题

主题 865|帖子 865|积分 2595

一、HarmonyOS Node-API简介

在HarmonyOS应用开发中,通常以ArkTS/JS语言为主,但在一些特殊场景下,例如游戏开发、物理模拟等,由于对性能、效率等有较高要求,需要借助现有的C/C++库来实现。为了满足这种需求,HarmonyOS引入了Node-API规范,通过封装I/O、CPU密集型、OS底层等能力,并对外暴露ArkTS/JS接口,从而实现了ArkTS/JS与C/C++之间的高效交互。
HarmonyOS Node-API是基于Node.js 12.x LTS的Node-API规范进行扩睁开发的一种机制,它为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。该机制提供了一组稳定且跨平台的API,能够在不同的操作系统上实现无缝利用。
在本文中,若无特殊阐明,后续提到的“Node-API”均指代HarmonyOS Node-API的相关能力。
关于HarmonyOS Node-API与Node.js 12.x LTS的Node-API规范在接口方面的异同点,可见Node-API参考。
其主要的应用场景包罗但不限于以下几种:


  • 系统功能开放:系统能够通过ArkTS/JS接口,将框架层丰富的模块功能开放给上层应用,方便开发者调用系统底层能力,实现更强大的功能扩展。
  • 性能优化与核心功能封装:应用开发者可以将对性能要求较高或需要底层系统调用的核心功能,利用C/C++进行封装实现,然后通过ArkTS/JS接口在应用中调用,从而显著进步应用的执行效率。
1.Node-API的组成架构

图1 Node-API的组成架构




  • Native Module:开发者利用Node-API开发的模块,用于在ArkTS侧导入利用。
  • Node-API:实现ArkTS与C/C++交互的逻辑。
  • ModuleManager:Native模块管理,包罗加载、查找等。
  • ScopeManager:管理napi_value的生命周期。
  • ReferenceManager:管理napi_ref的生命周期。
  • NativeEngine:ArkTS引擎抽象层,统一ArkTS引擎在Node-API层的接口举动。
  • ArkCompiler ArkTS Runtime:ArkTS运行时。
2.Node-API的关键交互流程

图2 Node-API的关键交互流程


ArkTS和C++之间的交互流程,主要分为以下两步:

  • 初始化阶段:当ArkTS侧在import一个Native模块时,ArkTS引擎会调用ModuleManager加载模块对应的so及其依赖。首次加载时会触发模块的注册,将模块界说的方法属性挂载到exports对象上并返回该对象。
  • 调用阶段:当ArkTS侧通过上述import返回的对象调用方法时,ArkTS引擎会找到并调用对应的C/C++方法。
二、利用Node-API实现跨语言交互开发流程

利用Node-API实现跨语言交互,起首需要按照Node-API的机制实现模块的注册和加载等相关动作。


  • ArkTS/JS侧:实现C++方法的调用。代码比力简朴,import一个对应的so库后,即可调用C++方法。
  • Native侧:.cpp文件,实现模块的注册。需要提供注册lib库的名称,并在注册回调方法中界说接口的映射关系,即Native方法及对应的JS/ArkTS接口名称等。
此处以在ArkTS/JS侧实现add()接口、在Native侧实现Add()接口,从而实现跨语言交互为例,出现利用Node-API进行跨语言交互的流程。
1.创建Native C++工程



  • 在DevEco Studio中New > Create Project,选择Native C++模板,点击Next,选择API版本,设置好工程名称,点击Finish,创建得到新工程。
  • 创建工程后工程结构可以分两部分,cpp部分和ets部分,工程结构具体介绍可见C++工程目次结构。
2.Native侧方法的实现



  • 设置模块注册信息
    ArkTS侧import native模块时,会加载其对应的so。加载so时,起首会调用napi_module_register方法,将模块注册到系统中,并调用模块初始化函数。
    napi_module有两个关键属性:一个是.nm_register_func,界说模块初始化函数;另一个是.nm_modname,界说模块的名称,也就是ArkTS侧引入的so库的名称,模块系统会根据此名称来区分不同的so。
  1. // entry/src/main/cpp/napi_init.cpp
  2. // 准备模块加载相关信息,将上述Init函数与本模块名等信息记录下来。
  3. static napi_module demoModule = {
  4. .nm_version = 1,
  5. .nm_flags = 0,
  6. .nm_filename = nullptr,
  7. .nm_register_func = Init,
  8. .nm_modname = "entry",
  9. .nm_priv = nullptr,
  10. .reserved = {0},
  11. };
  12. // 加载so时,该函数会自动被调用,将上述demoModule模块注册到系统中。
  13. extern "C" __attribute__((constructor)) void RegisterDemoModule() {
  14. napi_module_register(&demoModule);
  15. }
复制代码
注:以上代码无须复制,创建Native C++工程以后在napi_init.cpp代码中已设置好。


  • 模块初始化
    实现ArkTS接口与C++接口的绑定和映射。
  1. // entry/src/main/cpp/napi_init.cpp
  2. EXTERN_C_START
  3. // 模块初始化
  4. static napi_value Init(napi_env env, napi_value exports) {
  5. // ArkTS接口与C++接口的绑定和映射
  6. napi_property_descriptor desc[] = {
  7. // 注:仅需复制以下两行代码,Init在完成创建Native C++工程以后在napi_init.cpp中已配置好。
  8. {"callNative", nullptr, CallNative, nullptr, nullptr, nullptr, napi_default, nullptr},
  9. {"nativeCallArkTS", nullptr, NativeCallArkTS, nullptr, nullptr, nullptr, napi_default, nullptr}
  10. };
  11. // 在exports对象上挂载CallNative/NativeCallArkTS两个Native方法
  12. napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
  13. return exports;
  14. }
  15. EXTERN_C_END
复制代码


  • 在index.d.ts文件中,提供JS侧的接口方法。
  1. // entry/src/main/cpp/types/libentry/index.d.ts
  2. export const callNative: (a: number, b: number) => number;
  3. export const nativeCallArkTS: (cb: (a: number) => number) => number;
复制代码


  • 在oh-package.json5文件中将index.d.ts与cpp文件关联起来。
  1. // entry/src/main/cpp/types/libentry/oh-package.json5
  2. {
  3. "name": "libentry.so",
  4. "types": "./index.d.ts",
  5. "version": "",
  6. "description": "Please describe the basic information."
  7. }
复制代码


  • 在CMakeLists.txt文件中设置CMake打包参数。
  1. # entry/src/main/cpp/CMakeLists.txt
  2. cmake_minimum_required(VERSION 3.4.1)
  3. project(MyApplication2)
  4. set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
  5. include_directories(${NATIVERENDER_ROOT_PATH}
  6. ${NATIVERENDER_ROOT_PATH}/include)
  7. # 添加名为entry的库
  8. add_library(entry SHARED napi_init.cpp)
  9. # 构建此可执行文件需要链接的库
  10. target_link_libraries(entry PUBLIC libace_napi.z.so)
复制代码


  • 实现Native侧的CallNative以及NativeCallArkTS接口。具体代码如下:
  1. // entry/src/main/cpp/napi_init.cpp
  2. static napi_value CallNative(napi_env env, napi_callback_info info)
  3. {
  4. size_t argc = 2;
  5. // 声明参数数组
  6. napi_value args[2] = {nullptr};
  7. // 获取传入的参数并依次放入参数数组中
  8. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
  9. // 依次获取参数
  10. double value0;
  11. napi_get_value_double(env, args[0], &value0);
  12. double value1;
  13. napi_get_value_double(env, args[1], &value1);
  14. // 返回两数相加的结果
  15. napi_value sum;
  16. napi_create_double(env, value0 + value1, &sum);
  17. return sum;
  18. }
  19. static napi_value NativeCallArkTS(napi_env env, napi_callback_info info)
  20. {
  21. size_t argc = 1;
  22. // 声明参数数组
  23. napi_value args[1] = {nullptr};
  24. // 获取传入的参数并依次放入参数数组中
  25. napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
  26. // 创建一个int,作为ArkTS的入参
  27. napi_value argv = nullptr;
  28. napi_create_int32(env, 2, &argv );
  29. // 调用传入的callback,并将其结果返回
  30. napi_value result = nullptr;
  31. napi_call_function(env, nullptr, args[0], 1, &argv, &result);
  32. return result;
  33. }
复制代码
3.ArkTS侧调用C/C++方法实现

ArkTS侧通过import引入Native侧包含处理逻辑的so来利用C/C++的方法。
  1. // entry/src/main/ets/pages/Index.ets
  2. // 通过import的方式,引入Native能力。
  3. import nativeModule from 'libentry.so'
  4. @Entry
  5. @Component
  6. struct Index {
  7. @State message: string = 'Test Node-API callNative result: ';
  8. @State message2: string = 'Test Node-API nativeCallArkTS result: ';
  9. build() {
  10. Row() {
  11. Column() {
  12. // 第一个按钮,调用add方法,对应到Native侧的CallNative方法,进行两数相加。
  13. Text(this.message)
  14. .fontSize(50)
  15. .fontWeight(FontWeight.Bold)
  16. .onClick(() => {
  17. this.message += nativeModule.callNative(2, 3);
  18. })
  19. // 第二个按钮,调用nativeCallArkTS方法,对应到Native的NativeCallArkTS,在Native调用ArkTS function。
  20. Text(this.message2)
  21. .fontSize(50)
  22. .fontWeight(FontWeight.Bold)
  23. .onClick(() => {
  24. this.message2 += nativeModule.nativeCallArkTS((a: number)=> {
  25. return a * 2;
  26. });
  27. })
  28. }
  29. .width('100%')
  30. }
  31. .height('100%')
  32. }
  33. }
复制代码
4.Node-API的束缚限制

1)SO定名规则

导入利用的模块名和注册时的模块名大小写保持一致,如模块名为entry,则so的名字为libentry.so,napi_module中nm_modname字段应为entry,ArkTS侧利用时写作:import xxx from 'libentry.so'。
2)注册发起



  • nm_register_func对应的函数(如上述Init函数)需要加上static,防止与其他so里的符号冲突。
  • 模块注册的入口,即利用__attribute__((constructor))修饰的函数的函数名(如上述RegisterDemoModule函数)需要确保不与其它模块重复。
3)多线程限制

每个引擎实例对应一个JS线程,实例上的对象不能跨线程操作,否则会引起应用crash。利用时需要遵循如下原则:


  • Node-API接口只能在JS线程利用。
  • Native接口入参env与特定JS线程绑定只能在创建时的线程利用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

不到断气不罢休

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表