NAPI development for third-party library porting C/C++ and JS data type conversion
NAPI development for open source third-party library porting on open source OpenHarmony operating system. The key to HarmonyOS NEXT development ports and AppGallery publishing and future sideloading.
In "NAPI Development for Third-Party Library Porting —Hello OpenHarmony NAPI", the basic knowledge of NPAI interface development is explained through a Hello OpenHarmony NAPI sample. This article modifies the hellonapi.cpp file to describe the conversion between JS types and C/C++ data types. - The development is based on the latest OpenHarmony 3.2 Beta 3 version and its corresponding SDKs. The standard system development board is Runhe Software DAYU200.
The author has painstakingly studied some of the superficial aspects of the development of the third-party library NAPI, and shares the learning experience as follows: Demo video: https:// ost.51cto.com/show/18126
In this article you will become familiar with the NAPI framework:
How do I get the parameters passed by JS?
How to convert the parameters passed by JS (NAPI framework has been encapsulated as napi_value types) into C/C++ type values for calculation.
How to convert a value of a C/C++ type to a JS type and return it.
C/C++ and JS data types are converted through the NAPI framework
OpenHarmony NAPI encapsulates the eight data types defined in the ECMAScript standard: Boolean, Null, Undefined, Number, BigInt, String, Symbol, and Object, as well as the corresponding function types into napi_ The value type, which is described as the JS type in the following section, is used to receive data from the ArkUI application and return data to the ArkUI application.
ECMAScript is a scripting language standardized by ECMA-262 by Ecma International. This language is widely used on the World Wide Web, and it is often referred to as JavaScript or JScript, so it can be understood as a standard for JavaScript, but in fact the latter two are implementations and extensions of the ECMA-262 standard.
The following describes the specific details by extending a simple interface - Add(num1, num2), which is implemented in synchronous mode, and the extended API code processing process called by NAPI synchronous mode is shown in the following figure.
.cpp source code implementation
Modify the hellonapi.cpp files on the basis of the article "NAPI Development of Third-Party Library Porting[1]—Hello OpenHarmony NAPI", and the rest of the files remain unchanged.
hellonapi.cpp - the content is as follows:
#include <string.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
//NAPI The receiving parameters when defining the API method (interface service implementation) are (napi_env, napi_callback_info),
static napi_value Add(napi_env env, napi_callback_info info) {
//Get 2 parameters, the type of value is js type (napi_value)
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
//NAPI provides the napi_get_cb_info() method to get the parameter list, this, and other data from napi_callback_info
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
//Get and judge the js parameter type
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;
}
// Convert the parameter value of js type (napi_value) to C++ type 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));
//Convert the result from C++ type (double) to js type (napi_value)
//NAPI provides some methods to convert values of different types of C/C++ to node_value type and return them to JS code.
napi_value sum;
NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
//Return napi_value type result
return sum;
}
////api_addon_register_function
//2.Specify the processing function of the external interface registered by the module, and the specific extended interface is declared in this function
Static api_value register Func (api_env env, api_value export)
{
//In napi_property_descriptor desc[], the "Add echo method and the connection of another Js" of the Eight immortals "Add "square muscle method"
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 to exit;
}
// 1.First define napi_module and specify the module name corresponding to the current NAPI module
//And the processing function of the external interface registered by the module, the specific extended interface is declared in this function
//nm_modname: Module name, the corresponding eTS code is import nm_modname from'@ohos.ohos_shared_library_name'
//The corresponding eTS code for the example is: import hellonapi from'@ohos.hellonapi'
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc, // Module external interface registration function
.nm_modname = "hellonapi", // Custom module name
.nm_priv = ((void*)0),
.reserved = { 0 },
};
//3.After the module is defined, call the module registration function napi_module_register (napi_module*mod) provided by NAPI to register in the system.
//Register the module, this constructor function is automatically called when the device starts, and the module defined by the module is registered in the system
extern"C"__attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register (& hell on the api module);
}
.cpp source code parsing
Register the NAPI module and add an interface declaration
//napi_addon_register_function
//2.Specify the processing function of the external interface registered by the module, and the specific extended interface is declared in this function
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 to exit;
}
// 1.First define napi_module and specify the module name corresponding to the current NAPI module
//And the processing function of the external interface registered by the module, the specific extended interface is declared in this function
//nm_modname: Module name, the corresponding eTS code is import nm_modname from'@ohos.ohos_shared_library_name'
//The corresponding eTS code for the example is: import hellonapi from'@ohos.hellonapi'
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc, // Module external interface registration function
.nm_modname = "hellonapi", // Custom module name
.nm_priv = ((void*)0),
.reserved = { 0 },
};
//3.After the module is defined, call the module registration function napi_module_register (napi_module*mod) provided by NAPI to register in the system.
// register module, this constructor function is automatically called when the device starts, and the module defined by the module is registered in the system
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
Implement C/C++ code for interface services
//NAPI The receiving parameters when defining the API method (interface service implementation) are (napi_env, napi_callback_info),
//Where napi_callback_info is contextual information
static napi_value Add(napi_env env, napi_callback_info info) {
//Get 2 parameters, the type of value is js type (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));
//The NAPI framework provides the napi_typeof method for obtaining the specified js parameter type
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;
}
Convert the parameter value of the js type (napi_value) to the double C++ type
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));
Convert the result from a C++ type (double) to a js type (napi_value)
napi_value sum;
NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
Returns napi_value type results
return sum;
}
Get the parameters
static napi_value Add(napi_env env, napi_callback_info info) {
......
}
NAPI defines the received parameters for API methods as (napi_env, napi_callback_info)
Where napi_callback_info is contextual information.
NAPI provides napi_get_cb_info() method to get the parameter list, this, and other data from the napi_callback_info.
The napi_get_cb_info function is in the ohos3.2beta3 source code 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
The function description is as follows:
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)
Parameter description:
[in] env: The environment of the incoming interface caller, including the JS engine, etc., is provided by the framework, and can be passed in directly by default
[in] cbinfo: napi_callback_info object, contextual information
[in-out] argc: the length of the argv array. If the number of parameters contained in the napi_callback_info is greater than the requested number of argv, only the specified number of parameters in argv will be copied from the value of argc. If the actual number of parameters is less than the number requested, all parameters will be copied, the excess space of the array will be filled with null values, and the actual length of the parameters will be written to argc.
[out] argv: Used to receive a list of parameters
[out] this_arg: Used to receive this object
[out] data: NAPI context data Return value: The returned napi_ok indicates that the conversion is successful, and the other values fail. The following return napi_status the same method.
In the Add method, call the napi_get_cb_info function:
// The env and info parameters are passed in by the NAPI framework
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;
}
Convert JS values to C/C++ values
The arguments passed in this example are Javascript value types, which are encapsulated by the NAPI framework into a unified and unique type - the napi_value type, and in order to be able to do the calculation, we need to get the value of the type corresponding to the type in C/C++.
NAPI provides the following methods to obtain different types of values (ohos3.2beta3 source code: 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
nappy_get_value_external
napi_get_value_bigint_int64
napi_get_value_bigint_uint64
napi_get_value_bigint_words
In this example hellonapi.cpp the napi_get_value_double method is used, and the function is defined as follows:
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);
}
Parameter description: - [in] env: The environment of the incoming interface caller, including the JS engine, etc., is provided by the framework, and can be passed in directly by default. - [in] value: Passes in a napi_value type data object (which can be thought of as a JS object) to be converted. - [out] result: Convert the result of the corresponding type (double). Return Value: The return napi_ok indicates that the conversion is successful, and the other values fail.
Before obtaining the value of the C/C++ type of the parameter, you need to determine the type of the value, in this example, you need to determine that the JS value of the input parameter must be of number type - The NAPI framework provides napi_typeof methods to obtain the type of the specified object, and its function definition is as follows:
// 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);
}
Parameter description: - [in] env: The environment of the incoming interface caller, including the JS engine, etc., is provided by the framework, and can be passed in directly by default. - [in] value: Passes in a napi_value type data object (which can be thought of as a JS object) to be converted. - [out] result: Returns the JS type corresponding to the value parameter. - napi_valuetype corresponds to the eight data types defined in the ECMAScript standard: Boolean, Null, Undefined, Number, BigInt, String, Symbol, and Object, as well as the function type corresponding to the function. - In addition, the napi_valuetype includes a napi_external type that represents an object that has no properties and no prototypes.
To summarize the parameter type judgment and value conversion, the sample code is as follows:
static napi_value Add(napi_env env, napi_callback_info info) {
// 1. Get 2 parameters, the value type is js type (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. Get and determine the js parameter type
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
//Input data type exception handling
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
return NULL;
}
// 3. Convert the parameter value of js type (napi_value) into C++ type 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;
The calculation result is converted to JS type and returned
static napi_value Add(napi_env env, napi_callback_info info) {
···
// 4. Convert the result from C++ type (double) to js type (napi_value)
napi_value sum;
NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
···
}
The result of the calculation is a C/C++ type, which needs to be converted to a NAPI node_value type and returned to JS.
NAPI provides ways to convert different types of C/C++ values to node_value types and return them to JS code. For example:
napi_create_double
napi_create_int32
napi_create_uint32
napi_create_int64
napi_create_string_latin1
napi_create_string_utf8
napi_create_string_utf16
Taking the napi_create_double method as an example, the function is defined as follows:
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);
}
Parameter description: [in] env: The environment of the interface caller, including the js engine, etc., is provided by the framework, and can be passed in directly by default. [in] value: Pass in the double type data value to be converted. [out] result: Convert the result
ArkUI application implementation code
The ArkUI application implements a directory structure
index.ets reads as follows:
index.ets
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
export struct HelloNAPI {
private textInputController1: TextInputController = new TextInputController()
private textInputController2: TextInputController = new TextInputController()
private title: string = 'Data type conversion between C/C++ and JS'
private message: string = 'Calculate x+y'
private tipsNum1: string = 'Please enter X:'
private tipsNum2: string = 'Please enter Y:'
private tipsResult: string = 'Result:'
private buttonSubmit: string = 'Calculate'
@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 is the name of the ohos_shared_library structure defined in the BUILD.gn file
this.result = hellonapi.add(this.num1,this.num2)
})
}.height('30%').width('100%').justifyContent(FlexAlign.Center)
}
.width('100%')
}
.height('100%')
}
}
The renderings are as follows:
index.ets analysis
Parameter description
fieldtypeillustratetittlestringtitlemessagestringillustratetipsNum1numberThe first parameter is promptedtipsNum2numberPrompt for the second parametertipsResultstringPrompt the resultbuttonSubmitstringCalculate the button nameresultstringoutcomen1numberThe first number enterednum2numberThe second number entered
Set the parameters
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
export struct HelloNAPI {
private textInputController1: TextInputController = new TextInputController()
private textInputController2: TextInputController = new TextInputController()
private title: string = 'Data type conversion between C/C++ and JS'
private message: string = 'Calculate x+y'
private tipsNum1: string = 'Please enter X:'
private tipsNum2: string = 'Please enter Y:'
private tipsResult: string = 'Result:'
private buttonSubmit: string = 'Calculate'
@State result: number = 0.0
@State num1: number = 0.0
@State num2: number = 0.0
...
build() {
...
}
}
interface implementation
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 is the name of the ohos_shared_library structure defined in the BUILD.gn file
this.result = hellonapi.add(this.num1,this.num2)
})
}.height('30%').width('100%').justifyContent(FlexAlign.Center)
}
.width('100%')
}
.height('100%')
}
}
Bind Events and Associated Parameters The two TextInput components bind the onChange event and associate num1 and num2 respectively to record the input parameters
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 component to add a click event, call the Add method in the hellonapiu.cpp (call add in js, add and Add have been bound in the napi.cpp)
Row(){
Button(this.buttonSubmit)
.fontSize(37)
.fontWeight(FontWeight.Bold)
.margin({top:5})
.height(80)
.width(200)
.onClick(() => {
//hellonapi is the name of the ohos_shared_library structure defined in the BUILD.gn file
this.result = hellonapi.add(this.num1,this.num2)
})
The JS parameters of the Add function input to C through the NAPI framework are num1 and num2, and the output JS parameters are result
@ohos.hellonapi.d.ts interface documentation
declare namespace hellonapi {
export const add: (a: number, b: number) => number;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;
summary
hellonapi.cpp
index.ets
Join the community of Oniro OS, global version of OpenHarmony, developing distro for IoT market with Huawei HarmonyOS NEXT future
Today, I am opening a open public repo issue on Open Source ports, I want to contribute to the platform with VLC and Simple DirectMedia Layer third party library ports to boost the rich libraries warehouse of OpenHarmony access to all developers from desktop to mobile to everything else inbetween under IoT.
Translated: NAPI Development of Third-Party Library Migration[2]Data Type Conversion of C/C++ and JS - Zhihu (2022-10-23 04:24) Original, Chinese