在《三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI》通過一個Hello OpenHarmony NAPI樣例講述了NPAI接口開發基礎知識。本文在其基礎上修改hellonapi.cpp檔案,介紹JS類型和C/C++資料類型之間的轉換。
- 開發基于最新的OpenHarmony3.2Beta3版本及其對應SDK。标準系統開發闆為潤和軟體dayu200。
筆者刻苦學習了三方庫NAPI開發的一些皮毛,将學習經驗分享如下:
示範視訊:https://ost.51cto.com/show/18126
通過本文您将熟悉,通過NAPI架構:
- 如何擷取JS傳遞過來的參數。
- 如何将JS傳遞過來的參數(NAPI架構已封裝為napi_value類型)轉換成C/C++類型值用于計算。
- 如何将C/C++類型的值轉換成JS類型作并傳回。
(目錄)
通過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開發[2]C/C++與JS的資料類型轉換
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開發[2]C/C++與JS的資料類型轉換
- 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
附件連結:mysubsys子系統、arkui應用和接口檔案.zip