【最新鸿蒙开发】带你简单了解Native适配开发

引言

NDK(Native Development Kit)是HarmonyOS SDK提供的Native API、相应编译脚本和编译工具链的集合,方便开发者使用C或C++语言实现应用的关键功能。NDK只覆盖了HarmonyOS一些基础的底层能力,如C运行时基础库libc、图形库、窗口系统、多媒体、压缩库、面向ArkTS/JS与C跨语言的Node-API等,并没有提供ArkTS/JS API的完整能力。

  • NDK适用场景

    • 性能敏感的场景,如游戏,物理模拟等计算密集型场景

    • 需要复用已有C或C++库的场景

    • 需要针对CPU进行专项定制库的场景

今天,我们不聊别的,就专门来谈谈鸿蒙开发中的一大利器——Node-API。

Node-API简介

HarmonyOS Node-API是基于Node.js 8.x LTS的Node-API规范扩展开发的机制,为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。它提供了一组稳定的、跨平台的API,可以在不同的操作系统上使用。

  • Node-API的关键交互流程

    image-20240831162526192

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

    • 初始化阶段:当ArkTs侧在import一个Native模块时,ArkTS引擎会调用ModuleManager加载模块对应的so及其依赖。首次加载时会触发模块的注册,将模块定义的方法挂载到exports对象上并返回该对象

    • 调用阶段:当ArkTs侧通过上述import返回的对象调用方法时,ArkTs引擎会找到并调用对应的C/C++方法

Node-API的数据类型

一些常用的Node-API数据类型为:

数据类型描述
napi_status枚举数据类型,表示Node-API接口返回的状态信息,如操作成功与否的相关信息
napi_extended_error_info结构体,在调用函数不成功时存储了较为详细的错误信息
napi_value在C++代码中,表示一个JavaScript值
napi_env表示Node-API环境
napi_threadsafe_functionNode-API中的线程安全函数类型
napi_threadsafe_function_release_mode表示线程安全函数的释放模式
napi_threadsafe_function_call_mode表示线程安全函数的调用模式
napi_callback_infoNode-API回调信息类型
napi_callbackNode-API中的回调函数类型
napi_finalize表示资源清理函数类型
napi_async_execute_callback表示异步执行回调函数类型
napi_async_complete_callback表示异步完成回调函数类型
napi_cleanup_hook表示清理钩子函数类型
napi_async_cleanup_hook表示异步清理钩子函数类型
napi_throw表示抛出异常的函数
napi_throw_error表示抛出错误的函数
napi_throw_type_error表示抛出类型错误的函数
napi_throw_range_error表示抛出范围错误的函数
node_api_throw_syntax_error表示抛出语法错误的函数
napi_is_error表示检查错误的函数
napi_create_error表示创建错误的函数
napi_create_type_error表示创建类型错误的函数
napi_create_range_error表示创建范围错误的函数
node_api_create_syntax_error表示创建语法错误的函数
napi_get_and_clear_last_exception表示获取并清除最后异常的函数
napi_is_exception_pending表示检查是否有挂起的异常的函数
napi_fatal_exception表示处理致命异常的函数
napi_open_handle_scope表示打开句柄作用域的函数
napi_close_handle_scope表示关闭句柄作用域的函数
napi_open_escapable_handle_scope表示打开可逃逸句柄作用域的函数
napi_close_escapable_handle_scope表示关闭可逃逸句柄作用域的函数
napi_escape_handle表示逃逸句柄的函数
napi_create_reference表示创建引用的函数
napi_delete_reference表示删除引用的函数
napi_reference_ref表示引用引用计数的函数
napi_reference_unref表示解除引用引用计数的函数
napi_get_reference_value表示获取引用值的函数
napi_add_env_cleanup_hook表示添加环境清理钩子的函数

使用Node-API实现跨语言交互开发流程

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

  • ArkTS/JS侧:实现C++方法的调用。代码比较简单,import一个对应的so库后,即可调用C++方法。

  • Native侧:.cpp文件,实现模块的注册。需要提供注册lib库的名称,并在注册回调方法中定义接口的映射关系,即Native方法及对应的JS/ArkTS接口名称等。

  • 下面实现一个例子(参考官方)

  • image-20240902091523045

目录结构

Native侧

基于Node—API开发业务功能

//types/hello.cpp
//基于Node-API开发业务能力
static napi_value MyHypot(napi_env env, napi_callback_info info)
{
    if( (nullptr == env) || (nullptr == info)){
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot",  "env or exports is nullptr");
        return nullptr;
    }
    
    //参数个数
    size_t argc = 2;
    
    //声明参数数组
    napi_value args[2] = {nullptr};
    
    //获取参数
    if( napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)){
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot",  "api_get_cb_info failed");
        return nullptr;
    }
    
    //转化参数数组为double类型
    double valueX = 0.0;
    double valueY = 0.0;
    if( napi_ok != napi_get_value_double(env, args[0], &valueX) || napi_ok != napi_get_value_double(env, args[1], &valueY)){
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot",  "api_get_value_double failed");
        return nullptr;
    }
    
    //使用hypot方法计算结果
    double result = hypot(valueX, valueY);
    
    napi_value napiResult;
    if( napi_ok != napi_create_double(env, result, &napiResult)){
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot",  "api_create_double failed");
        return nullptr;
    }
    return napiResult;
}

 接口映射

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    if( (nullptr == env) || (nullptr == exports)){
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init",  "env or exports is nullptr");
        return exports;
    }
    // 要添加的方法,这里根据方法修改数组
    napi_property_descriptor desc[] = {
        { "myHypot", nullptr, MyHypot, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    if( napi_ok != napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)){
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init",  "api_define_properties failed");
        return nullptr;
    }
    return exports;
}
EXTERN_C_END

 模块注册


static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    //名字要为接口映射定义的函数名
    .nm_register_func = Init,
    //定义模块名称,ArkTs侧引入so库的名称
    .nm_modname = "hello",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};


extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

 模块配置构建(CMakeLists)

# the minimum version of CMake.
//types/CMakeLists
cmake_minimum_required(VERSION 3.5.0)
project(mycppdemo)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

find_library(
    #查找hilog_ndk.z库的位置,存储到hilog-lib的中,适用于不清楚依赖库的具体路径
    hilog-lib
    hilog_ndk.z
)
//注意下面的两个hello
//由hello.cpp生成动态库,这里前面叫做hello,编译出来后就是libhello.so
add_library(hello SHARED hello.cpp)
//设置目标需要链接的库
target_link_libraries(hello PUBLIC ${hilog-lib} libace_napi.z.so libc++.a)
//build-profile.json5
{
  "apiType": 'stageMode',
  "buildOption": {
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "arguments": "",
      "abiFilters": [
        "arm64-v8a",
        "x86_64"
      ],
      "cppFlags": "",
    }
  },
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    }
  ]
}

导出Native接口1

//types/libhello/index.d.ts
//libhello与编译后的名称对应
export const myHypot: (a: number, b: number) => number;

导出Native接口2

//oh-package.json5
{
  "license": "",
  "devDependencies": {
    "@types/libhello.so": "file:./src/main/cpp/types/libhello"
  },
  "author": "",
  "name": "entry",
  "description": "Please describe the basic information.",
  "main": "",
  "version": "1.0.0",
  "dependencies": {}
}

ArkTs侧

ets文件中使用Native-API

import libHello from 'libhello.so';
import CommonContants from '../../common/CommonContants';

@Entry
@Component
struct NativeTemplate {
  @State result: string = '0';
  @State numX: number = 0.0;
  @State numY: number = 0.0;
  private textInputControllerX: TextInputController = new TextInputController();
  private textInputControllerY: TextInputController = new TextInputController();

  build() {
    Column() {
      //...
      Row() {
        Button($r('app.string.submit_button'))
          .fontSize($r('app.float.submit_button_font_size'))
          .fontWeight(CommonContants.FONT_WEIGHT)
          .height(CommonContants.FULL_PARENT)
          .width($r('app.float.button_width'))
          .onClick(() => {
            //使用c++函数
            let resultTemp = libHello.myHypot(this.numX, this.numY);
            if (resultTemp > CommonContants.MAX_RESULT) {
              this.result = resultTemp.toExponential(CommonContants.EXPONENTIAL_COUNT);
            } else {
              this.result = resultTemp.toFixed(CommonContants.FIXED_COUNT);
            }
          })
      }
      .height($r('app.float.button_height'))
      .width(CommonContants.FULL_PARENT)
      .justifyContent(FlexAlign.Center)
      .margin({ top: $r('app.float.button_margin_top') })
    }
    .width(CommonContants.FULL_PARENT)
    .height(CommonContants.FULL_PARENT)
    .backgroundColor($r('app.color.background_color'))
  }
}

CommonContants.ets文件
 

export default class CommonConstants {

  /**
   * Height and width is 100%.
   * */
  static readonly FULL_PARENT: string = '100%';

  /**
   * Font weight.
   * */
  static readonly FONT_WEIGHT: number = 500;

  /**
   * Max lines.
   */
  static readonly MAX_LINES: number = 1;

  /**
   * TextInput layout weight.
   */
  static readonly TEXTINPUT_LAYOUT_WEIGHT: number = 1;

  /**
   * Bigger than 999999999 using scientific notation.
   */
  static readonly MAX_RESULT: number = 999999999;

  /**
   * Exponential count.
   */
  static readonly EXPONENTIAL_COUNT = 9;

  /**
   * Fixed count.
   */
  static readonly FIXED_COUNT = 2;
}

结语

通过上面的示例,我们可以看到,使用Node-API实现跨语言交互开发流程并不复杂。只需要掌握好相关的接口和步骤,你就能够轻松地在C/C++和ets文件之间进行数据传输和函数调用。当然,这只是一个简单的示例,实际开发中可能会遇到更加复杂的情况和问题。但无论如何,我相信只要你肯下功夫去学习和实践,你一定能够成为鸿蒙开发的佼佼者!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值