引言
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的关键交互流程
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_function | Node-API中的线程安全函数类型 |
napi_threadsafe_function_release_mode | 表示线程安全函数的释放模式 |
napi_threadsafe_function_call_mode | 表示线程安全函数的调用模式 |
napi_callback_info | Node-API回调信息类型 |
napi_callback | Node-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接口名称等。
-
下面实现一个例子(参考官方)
目录结构
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文件之间进行数据传输和函数调用。当然,这只是一个简单的示例,实际开发中可能会遇到更加复杂的情况和问题。但无论如何,我相信只要你肯下功夫去学习和实践,你一定能够成为鸿蒙开发的佼佼者!
标签:Node,LOG,鸿蒙,适配,函数,nullptr,API,napi,Native From: https://blog.csdn.net/m0_66825548/article/details/142033048