通过NAPI框架进行C/C++与JS数据类型的转换
- OpenHarmony NAPI将ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型,以及函数对应的Function类型,统一封装成napi_value类型,下文中表述为JS类型,用于接收ArkUI应用传递过来的数据及返回数据给ArkUI应用。
ECMAScript是一种由Ecma国际(通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,所以它可以理解为是JavaScript的一个标准,但实际上后两者是ECMA-262标准的实现和扩展。
- 下面通过扩展一个简单的接口——Add(num1, num2)讲述具体细节,接口使用同步方式实现,NAPI的同步方式调用的扩展API代码处理流程如下图。
.cpp源码实现
- 在《三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI 》一文的基础上修改hellonapi.cpp文件,其余文件不变。
- hellonapi.cpp内容如下:
#include <string.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
//NAPI定义API方法(接口业务实现)时的接收参数为(napi_env, napi_callback_info),
static napi_value Add(napi_env env, napi_callback_info info) {
//获取2个参数,值的类型是js类型(napi_value)
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
//NAPI提供了napi_get_cb_info()方法可从napi_callback_info中获取参数列表、this及其他数据
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
//获取并判断js参数类型
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
return NULL;
}
//将js类型(napi_value)的参数值转换成C++类型double
double value0;
NAPI_CALL(env, napi_get_value_double(env, args[0], &value0));
double value1;
NAPI_CALL(env, napi_get_value_double(env, args[1], &value1));
//将结果由C++类型(double)转换成js类型(napi_value)
//NAPI提供了一些方法以便将C/C++不同类型的值转为node_value类型,返回给JS代码。
napi_value sum;
NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
//返回napi_value类型结果
return sum;
}
// napi_addon_register_func
//2.指定模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
static napi_value registerFunc(napi_env env, napi_value exports)
{
// 在napi_property_descriptor desc[]中将编写C的“Add方法与对外暴露Js的接口“add”方法进行关联
static 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;
}
// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
//示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc, // 模块对外接口注册函数
.nm_modname = "hellonapi", // 自定义模块名
.nm_priv = ((void*)0),
.reserved = { 0 },
};
//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
.cpp源码解析
注册NAPI模块、添加接口声明
// napi_addon_register_func
//2.指定模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
static napi_value registerFunc(napi_env env, napi_value exports)
{
static 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;
}
// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
//示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc, // 模块对外接口注册函数
.nm_modname = "hellonapi", // 自定义模块名
.nm_priv = ((void*)0),
.reserved = { 0 },
};
//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
接口业务实现C/C++代码
//NAPI定义API方法(接口业务实现)时的接收参数为(napi_env, napi_callback_info),
//其中napi_callback_info为上下文的信息
static napi_value Add(napi_env env, napi_callback_info info) {
//获取2个参数,值的类型是js类型(napi_value)
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
//NAPI框架提供了napi_typeof方法用于获取指定js参数类型
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
return NULL;
}
//将js类型(napi_value)的参数值转换成C++类型double
double value0;
NAPI_CALL(env, napi_get_value_double(env, args[0], &value0));
double value1;
NAPI_CALL(env, napi_get_value_double(env, args[1], &value1));
//将结果由C++类型(double)转换成js类型(napi_value)
napi_value sum;
NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
//返回napi_value类型结果
return sum;
}
获取参数
static napi_value Add(napi_env env, napi_callback_info info) {
......
}
-
NAPI定义API方法时的接收参数为(napi_env, napi_callback_info)
- 其中napi_callback_info为上下文的信息。
-
NAPI提供了napi_get_cb_info()方法可从napi_callback_info中获取参数列表、this及其他数据。
napi_get_cb_info函数在ohos3.2beta3源码foundation/arkui/napi/native_engine/native_api.cpp中
// Methods to work with napi_callbacks
// Gets all callback info in a single call. (Ugly, but faster.)
NAPI_EXTERN napi_status napi_get_cb_info(napi_env env, // [in] NAPI environment handle
napi_callback_info cbinfo, // [in] Opaque callback-info handle
size_t* argc, // [in-out] Specifies the size of the provided argv array
// and receives the actual count of args.
napi_value* argv, // [out] Array of values
napi_value* this_arg, // [out] Receives the JS 'this' arg for the call
void** data) // [out] Receives the data pointer for the callback.
{
CHECK_ENV(env);
CHECK_ARG(env, cbinfo);
auto info = reinterpret_cast<NativeCallbackInfo*>(cbinfo);
if ((argc != nullptr) && (argv != nullptr)) {
size_t i = 0;
for (i = 0; (i < *argc) && (i < info->argc); i++) {
argv[i] = reinterpret_cast<napi_value>(info->argv[i]);
}
*argc = i;
}
if (argc != nullptr) {
*argc = info->argc;
}
if (this_arg != nullptr) {
*this_arg = reinterpret_cast<napi_value>(info->thisVar);
}
if (data != nullptr && info->functionInfo != nullptr) {
*data = info->functionInfo->data;
}
return napi_clear_last_error(env);
}
napi_get_cb_info
函数说明如下:
napi_status napi_get_cb_info(napi_env env,
napi_callback_info cbinfo,
size_t* argc,
napi_value* argv,
napi_value* this_arg,
void** data)
-
参数说明:
- [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可
- [in] cbinfo: napi_callback_info对象,上下文的信息
- [in-out] argc: argv数组的长度。若napi_callback_info中实际包含的参数的个数大于请求的数量argc,将只复制argc的值所指定数量的参数只argv中。若实际的参数个数小于请求的数量,将复制全部的参数,数组多余的空间用空值填充,并将参数实际长度写入argc。
- [out] argv: 用于接收参数列表
- [out] this_arg: 用于接收this对象
- [out] data: NAPI的上下文数据 返回值:返回napi_ok表示转换成功,其他值失败。下面的返回napi_status方法一样。
-
在Add方法中,调用napi_get_cb_info函数:
// env、info 参数由NAPI框架传入
static napi_value Add(napi_env env, napi_callback_info info) {
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
napi_value sum;
return sum;
}
JS类型值转换为C/C++类型的值
- 此示例中传入的参数是Javascript值类型,被NAPI框架封装成统一的唯一类型——napi_value类型,为了能够进行计算,我们需要获取其对应在C/C++中的类型的值。
- NAPI提供了包括以下方法以便获取不同类型的值(ohos3.2beta3源码foundation/arkui/napi/native_engine/native_api.cpp中)
- napi_get_value_double
- napi_get_value_int32
- napi_get_value_uint32
- napi_get_value_int64
- napi_get_value_bool
- napi_get_value_string_latin1(Copies LATIN-1 encoded bytes from a string into a buffer)
- napi_get_value_string_utf8(Copies UTF-8 encoded bytes from a string into a buffer)
- napi_get_value_string_utf16
- napi_get_value_external
- napi_get_value_bigint_int64
- napi_get_value_bigint_uint64
- napi_get_value_bigint_words
- 此示例hellonapi.cpp中使用到了napi_get_value_double方法,函数定义如下:
- NAPI提供了包括以下方法以便获取不同类型的值(ohos3.2beta3源码foundation/arkui/napi/native_engine/native_api.cpp中)
NAPI_EXTERN napi_status napi_get_value_double(napi_env env, napi_value value, double* result)
{
CHECK_ENV(env);
CHECK_ARG(env, value);
CHECK_ARG(env, result);
auto nativeValue = reinterpret_cast<NativeValue*>(value);
RETURN_STATUS_IF_FALSE(env, nativeValue->TypeOf() == NATIVE_NUMBER, napi_number_expected);
*result = *reinterpret_cast<NativeNumber*>(nativeValue->GetInterface(NativeNumber::INTERFACE_ID));
return napi_clear_last_error(env);
}
参数说明:
- [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
- [in] value: 传入要转换的napi_value类型数据对象(可视为一个JS对象)。
- [out] result: 转换出对应类型(double)结果。 返回值:返回napi_ok表示转换成功,其他值失败。
获取参数的C/C++类型的值前,需要先判断值的类型,本示例需要判断传入参数的JS值必须为number类型
- NAPI框架提供了napi_typeof方法用于获取指定对象的类型,其函数定义如下:
// Methods to get the native napi_value from Primitive type
NAPI_EXTERN napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result)
{
CHECK_ENV(env);
CHECK_ARG(env, value);
CHECK_ARG(env, result);
auto nativeValue = reinterpret_cast<NativeValue*>(value);
*result = (napi_valuetype)nativeValue->TypeOf();
return napi_clear_last_error(env);
}
参数说明:
- [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
- [in] value: 传入要转换的napi_value类型数据对象(可视为一个JS对象)。
- [out] result: 返回value参数对应的JS类型。
- napi_valuetype对应了ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型,以及函数对应的Function类型。
- 另外,napi_valuetype还包括了一个napi_external类型,其表示没有任何属性也没有任何原型的对象。
综上所述参数类型判断及值转换,示例代码如下:
static napi_value Add(napi_env env, napi_callback_info info) {
// 1\. 获取2个参数,值的类型是js类型(napi_value)
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
// 2\. 获取并判断js参数类型
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
//输入的数据类型异常处理
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
return NULL;
}
// 3\. 将js类型(napi_value)的参数值转换成C++类型double
double value0;
NAPI_CALL(env, napi_get_value_double(env, args[0], &value0));
double value1;
NAPI_CALL(env, napi_get_value_double(env, args[1], &value1));
napi_value sum;
return sum;
计算结果转换为JS类型并返回
static napi_value Add(napi_env env, napi_callback_info info) {
···
// 4\. 将结果由C++类型(double)转换成js类型(napi_value)
napi_value sum;
NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
···
}
-
计算的结果是C/C++类型,需要转换成NAPI node_value类型返回给JS。
-
NAPI提供了一些方法以便将C/C++不同类型的值转为node_value类型,返回给JS代码。例如:
- napi_create_double
- napi_create_int32
- napi_create_uint32
- napi_create_int64
- napi_create_string_latin1
- napi_create_string_utf8
- napi_create_string_utf16
-
以napi_create_double方法为例,函数定义如下:
NAPI_EXTERN napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result)
{
CHECK_ENV(env);
CHECK_ARG(env, result);
auto engine = reinterpret_cast<NativeEngine*>(env);
auto resultValue = engine->CreateNumber(value);
*result = reinterpret_cast<napi_value>(resultValue);
return napi_clear_last_error(env);
}
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可.
[in] value: 传入要转换的double类型数据值.
[out] result: 转换出结果
ArkUI应用实现代码
ArkUI应用实现目录结构
index.ets内容如下:
index.ets
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
export struct HelloNAPI {
private textInputController1: TextInputController = new TextInputController()
private textInputController2: TextInputController = new TextInputController()
private tittle: string = 'C/C++与JS的数据类型转换'
private message: string = '计算x+y'
private tipsNum1: string = '请输入X:'
private tipsNum2: string = '请输入Y:'
private tipsResult: string = '结果:'
private buttonSubmit: string = '计算'
@State result: number = 0.0
@State num1: number = 0.0
@State num2: number = 0.0
build() {
Row() {
Column() {
Row(){
Text(this.tittle).height('100%').align(Alignment.Center).fontSize(50).fontWeight(800)
}.height('30%').width('100%').justifyContent(FlexAlign.Center)
Row(){
Text(this.message).height('100%').align(Alignment.Center).fontSize(35).fontWeight(500)
}.height('15%').width('100%').justifyContent(FlexAlign.Center)
Row(){
Text(this.tipsNum1).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
TextInput({ placeholder: 'X', controller:this.textInputController1}).type(InputType.Number)
.height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
.onChange(value =>{this.num1 = parseFloat(value)})
}.height('6%').width('100%').justifyContent(FlexAlign.Start)
Row(){
Text(this.tipsNum2).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
TextInput({ placeholder: 'Y', controller:this.textInputController2}).type(InputType.Number)
.height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
.onChange(value =>{this.num2 = parseFloat(value)})
}.height('6%').width('100%').margin({top:20})
Row(){
Text(this.tipsResult).fontColor(Color.Black).fontSize(35).width('40%').height('100%').margin({left:30})
Text(''+this.result).fontColor(Color.Black).fontSize(35).width('60%').height('100%')
}.height('10%').width('100%').touchable(false)
Row(){
Button(this.buttonSubmit)
.fontSize(37)
.fontWeight(FontWeight.Bold)
.margin({top:5})
.height(80)
.width(200)
.onClick(() => {
//hellonapi为BUILD.gn文件中定义的ohos_shared_library结构体名称
this.result = hellonapi.add(this.num1,this.num2)
})
}.height('30%').width('100%').justifyContent(FlexAlign.Center)
}
.width('100%')
}
.height('100%')
}
}
效果图如下:
index.ets解析
- 参数说明
字段 | 类型 | 说明 |
---|---|---|
tittle | string | 标题 |
message | string | 说明 |
tipsNum1 | number | 提示输入第一个参数 |
tipsNum2 | number | 提示输入第二个参数 |
tipsResult | string | 提示结果 |
buttonSubmit | string | 计算按钮名称 |
result | string | 结果 |
num1 | number | 输入的第一个数 |
num2 | number | 输入的第二个数 |
- 设置参数
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
export struct HelloNAPI {
private textInputController1: TextInputController = new TextInputController()
private textInputController2: TextInputController = new TextInputController()
private tittle: string = 'C/C++与JS的数据类型转换'
private message: string = '计算x+y'
private tipsNum1: string = '请输入X:'
private tipsNum2: string = '请输入Y:'
private tipsResult: string = '结果:'
private buttonSubmit: string = '计算'
@State result: number = 0.0
@State num1: number = 0.0
@State num2: number = 0.0
...
build() {
...
}
}
- 界面实现
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
export struct HelloNAPI {
...
build() {
Row() {
Column() {
Row(){
Text(this.tittle).height('100%').align(Alignment.Center).fontSize(50).fontWeight(800)
}.height('30%').width('100%').justifyContent(FlexAlign.Center)
Row(){
Text(this.message).height('100%').align(Alignment.Center).fontSize(35).fontWeight(500)
}.height('15%').width('100%').justifyContent(FlexAlign.Center)
Row(){
Text(this.tipsNum1).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
TextInput({ placeholder: 'X', controller:this.textInputController1}).type(InputType.Number)
.height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
.onChange(value =>{this.num1 = parseFloat(value)})
}.height('6%').width('100%').justifyContent(FlexAlign.Start)
Row(){
Text(this.tipsNum2).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
TextInput({ placeholder: 'Y', controller:this.textInputController2}).type(InputType.Number)
.height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
.onChange(value =>{this.num2 = parseFloat(value)})
}.height('6%').width('100%').margin({top:20})
Row(){
Text(this.tipsResult).fontColor(Color.Black).fontSize(35).width('40%').height('100%').margin({left:30})
Text(''+this.result).fontColor(Color.Black).fontSize(35).width('60%').height('100%')
}.height('10%').width('100%').touchable(false)
Row(){
Button(this.buttonSubmit)
.fontSize(37)
.fontWeight(FontWeight.Bold)
.margin({top:5})
.height(80)
.width(200)
.onClick(() => {
//hellonapi为BUILD.gn文件中定义的ohos_shared_library结构体名称
this.result = hellonapi.add(this.num1,this.num2)
})
}.height('30%').width('100%').justifyContent(FlexAlign.Center)
}
.width('100%')
}
.height('100%')
}
}
- 绑定事件、关联参数
两个TextInput组件分别绑定onChange事件,并分别关联num1,num2来记录输入的参数
Row(){
Text(this.tipsNum1).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
TextInput({ placeholder: 'X', controller:this.textInputController1}).type(InputType.Number)
.height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
.onChange(value =>{this.num1 = parseFloat(value)})
}.height('6%').width('100%').justifyContent(FlexAlign.Start)
Row(){
Text(this.tipsNum2).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
TextInput({ placeholder: 'Y', controller:this.textInputController2}).type(InputType.Number)
.height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
.onChange(value =>{this.num2 = parseFloat(value)})
}.height('6%').width('100%').margin({top:20})
- Button组件添加点击事件,调用hellonapiu.cpp中的Add方法(调用js中的add,add和Add已经在napi.cpp中绑定)
Row(){
Button(this.buttonSubmit)
.fontSize(37)
.fontWeight(FontWeight.Bold)
.margin({top:5})
.height(80)
.width(200)
.onClick(() => {
//hellonapi为BUILD.gn文件中定义的ohos_shared_library结构体名称
this.result = hellonapi.add(this.num1,this.num2)
})
- 通过NAPI框架输入到C的Add函数的JS参数是num1和num2,输出的JS参数是result
@ohos.hellonapi.d.ts接口文档
declare namespace hellonapi {
export const add: (a: number, b: number) => number;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;
总结
hellonapi.cpp
index.ets
为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
《鸿蒙开发学习手册》:
如何快速入门:https://qr21.cn/FV7h05
- 基本概念
- 构建第一个ArkTS应用
- ……
开发基础知识:https://qr21.cn/FV7h05
- 应用基础知识
- 配置文件
- 应用数据管理
- 应用安全管理
- 应用隐私保护
- 三方应用调用管控机制
- 资源分类与访问
- 学习ArkTS语言
- ……
基于ArkTS 开发:https://qr21.cn/FV7h05
- Ability开发
- UI开发
- 公共事件与通知
- 窗口管理
- 媒体
- 安全
- 网络与链接
- 电话服务
- 数据管理
- 后台任务(Background Task)管理
- 设备管理
- 设备使用信息统计
- DFX
- 国际化开发
- 折叠屏系列
- ……
鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH
鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH
1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向