[Tactile intelligence RK3568 user experience] NAPI class object export and life cycle management

4. Analysis of sample project source code

  • The template of the project is Native C++, and the model is Stage.
  • Source code analysis mainly revolves around the following files

4.1. The specific implementation of NAPI exported objects and life cycle management

4.1.1. Define NapiTest class and method

  • The contents of the Napi.h file are as follows:

    #ifndef __NAPI_TEST_H__
    #define __NAPI_TEST_H__
    
    #include "napi/native_api.h"
    #include <js_native_api_types.h>
    
    #include <iostream>
    
    #define NAPI_CLASS_NAME "NapiTestClass"
    
    class NapiTest {
    public:
    NapiTest() : mEnv(nullptr), mRef(nullptr) {
    }
      
    NapiTest(napi_env env) : mEnv(env), mRef(nullptr){
    }
    ~NapiTest();
      
      // Create an entity of the NapiTest class and return the entity to the application side. This method creates a class entity for js, so the interface needs to be exported
    static napi_value Create(napi_env env, napi_callback_info info);
      
      // Initialize the js class and set the corresponding properties and export it   
    static napi_value Init(napi_env env, napi_value exports);         
    
    
    private:
      
      // Set data, this method is directly called to js, ​​so this interface needs to be exported
      static napi_value SetMsg(napi_env env, napi_callback_info info);
      
      // To get data, this method is directly called by js, so this interface needs to be exported to the outside world    
      static napi_value GetMsg(napi_env env, napi_callback_info info);
      
      // The actual construction function when defining the js structure
      static napi_value Constructor(napi_env env, napi_callback_info info);     
    
      // A function that releases resources (similar to a class destructor)    
      static void Destructor(napi_env env, void *nativeObject, void *finalize); 
    
      // lifecycle variables    
      static napi_ref sConstructor_;  
    
      // Variables for setting and getting data    
      static std::string _msg;        
    
      // Record environment variables    
      napi_env mEnv = nullptr;        
    
      // Document lifecycle variables    
      napi_ref mRef = nullptr;        
    
    };
    
    #endif  /* __NAPI_TEST_H__ */
4.1.1.1 napi_value
  • The value of Node.js Node-API is represented by napi_value type.
    OpenHarmony NAPI encapsulates the eight data types Boolean, Null, Undefined, Number, BigInt, String, Symbol, and Object defined in the ECMAScript standard, as well as the Function type corresponding to the function, into a napi_value type, which is expressed as the JS type below for Receive the data passed by the ArkUI application and return the data to the ArkUI application.
  • This is an opaque pointer used to represent JavaScript values.
4.1.1.2 napi_ref
4.1.1.3 napi_env

4.1.2 Define the NapiTest class as a js class

4.1.2.1 Before defining the js class, you need to set the export method of the js class
    // Before defining the js class, you need to set the export method of the class
    napi_property_descriptor desc[] = {
        { "getMsg", nullptr, NapiTest::GetMsg, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "setMsg", nullptr, NapiTest::SetMsg, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "create", nullptr, NapiTest::Create, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
4.1.2.1.1 napi_property_descriptor

refer to https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_nap...

Node.js Node-API has a set of APIs to get and set properties of JavaScript objects. In JavaScript, properties are represented as a tuple of a key and a value. Basically, all property keys in Node-API can be represented in any of the following forms:

  • Named: a simple UTF-8 encoded string
  • Integer-Indexed: index value, represented by uint32_t
  • JavaScript value: represented by napi_value in Node-API. It can be a napi_value representing a string, number or symbol.
typedef struct {
  // One of utf8name and name must be NULL
  const char* utf8name;
  napi_value name;

  napi_callback method;
  napi_callback getter;
  napi_callback setter;
  napi_value value;

  napi_property_attributes attributes;
  void* data;
} napi_property_descriptor;

Parameter analysis:

  • utf8name: The method name exported by the js class set before defining the js class, encoded as UTF8. One of utf8name or name must be provided for this attribute. (One of utf8name and name must be NULL)
  • name: optional napi_value, pointing to a JavaScript string or symbol to use as the property's key. One of utf8name or name must be provided for this attribute.
  • method: Set the value property of the property descriptor object to the JavaScript function represented by method. If this parameter is passed, set value, getter and setter to NULL (because these members will not be used).
  • attributes: Attributes associated with a particular attribute.
  • data: The callback data passed to the method, getter, and setter when calling the function.
4.1.2.2 Define JavaScript classes corresponding to C++ classes
    napi_value constructor = nullptr;

    // Define JavaScript classes corresponding to C++ classes
    if (napi_define_class(env, NAPI_CLASS_NAME, NAPI_AUTO_LENGTH, Constructor, nullptr, sizeof(desc) / sizeof(desc[0]),
                          desc, &constructor) != napi_ok) {
        // "!=" is used to check if the values ​​of the two operands are equal, if not equal then the condition is true
        return nullptr;
    }
4.1.2.2.1 napi_define_class

napi_define_class function description:
https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_nap...

napi_status napi_define_class(napi_env env,
                          const char* utf8name,
                          size_t length,
                          napi_callback constructor,
                          void* data,
                          size_t property_count,
                          const napi_property_descriptor* properties,
                          napi_value* result);

Function: Define JavaScript classes corresponding to C++ classes.
Parameter Description:

  • [in] env: the environment for calling the api
  • [in] utf8name: The name of the C++ class
  • [in] length: the length of the name of the C++ class, the default automatic length uses NAPI_AUTO_LENGTH
  • [in] constructor: The callback function that handles the construction of C++ class instances (because the Constructor function is called by napi_define_class). When exporting a C++ class object, this function must be a static member with the napi_callback signature (the Constructor function has the napi_callback signature to satisfy the form of typedef napi_value (*napi_callback)(napi_env, napi_callback_info);). c++ class constructors cannot be used.
  • [in] data: Optional data passed to the constructor callback as the data attribute of the callback information
  • [in] property_count: The number of parameters in the property array
  • [in] properties: property array, see the napi_property_descriptor part of the code for details
  • [out] result: the napi_value object bound to the class instance through the class constructor
    Returns: napi_ok if the API call is successful.

JS constructor
If a js function is called using the new operator, then this function is called a js constructor

C++ class callback function
We call other people's API called call, and the called third-party API calls our function called callback (callback)

4.1.2.3 Realize the constructor of js class

When the ArkTS application obtains the class object through the new method on the js side, the constructor callback function set in napi_define_class will be called at this time. The implementation method of this function is as follows:

napi_value NapiTest::Constructor(napi_env env, napi_callback_info info)
{
    napi_value undefineVar = nullptr, thisVar = nullptr;
    napi_get_undefined(env, &undefineVar);

    // Get the parameter object passed in, the object is not empty, create an instance according to the parameter and bind to the object
    if (napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr) == napi_ok && thisVar != nullptr) {

        // Create a NapiTest instance
        NapiTest *reference = new NapiTest(env);

        // Bind an instance to an object and get the lifecycle of the object
        if (napi_wrap(env, thisVar, reinterpret_cast<void *>(reference), NapiTest::Destructor, nullptr, &(reference->mRef)) == napi_ok) {
            return thisVar;
        }

        return thisVar;
    }
    
    return undefineVar;
}

void NapiTest::Destructor(napi_env env, void *nativeObject, void *finalize)
{
    // release resources
    NapiTest *test = reinterpret_cast<NapiTest*>(nativeObject);
    test->~NapiTest();
}
  • The NapiTest::Destructo method is used to release the created object:

    void NapiTest::Destructor(napi_env env, void *nativeObject, void *finalize)
    {
      // Class destructor, freeing resources
      NapiTest *test = reinterpret_cast<NapiTest*>(nativeObject);
      test->~NapiTest();
    }
4.1.2.3.1 napi_wrap
napi_status napi_wrap(napi_env env,
                  napi_value js_object,
                  void* native_object,
                  napi_finalize finalize_cb,
                  void* finalize_hint,
                  napi_ref* result);

Function: Bind the C++ class instance to the js object and associate the corresponding life cycle
Parameter Description:

  • [in] env: the environment for calling the api
  • [in] js_object: js object bound to native_object
  • [in] native_object: C++ class instance object
  • [in] finalize_cb: the callback function to release the instance object
  • [in] finalize_hint: the data passed to the callback function
  • [out] result: a reference to the bound js object

Return: return 0 if the call succeeds, otherwise return

4.1.2.3.2 napi_get_cb_info

NAPI provides the napi_get_cb_info() method to get the parameter list, this and other data from napi_callback_info. This method is used in the constructor callback function to retrieve detailed information about the call, such as parameters and This pointer, from the given callback information.

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., provided by the framework, by default, it can be passed in directly
  • [in] cbinfo: napi_callback_info object, context information
  • [in-out] argc: The length of the argv array. If the actual number of parameters contained in napi_callback_info is greater than the requested number argc, only the number of parameters specified by the value of argc will be copied to argv. If the actual number of parameters is less than the requested number, all parameters will be copied, the extra space in the array will be filled with null values, and the actual length of the parameters will be written into argc.
  • [out] argv: used to receive the parameter list
  • [out] this_arg: used to receive this object
  • [out] data: NAPI context data Return value: return napi_ok means the conversion is successful, other values ​​fail. The following return napi_status method is the same.

4.1.3 Export js class

    // Create a life cycle with an initial reference count set to 1
    if (napi_create_reference(env, constructor, 1, &sConstructor_) != napi_ok) {
        return nullptr;
    }

    // Set the relevant properties of the NapiTest object and bind it to the export variable exports
    if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) != napi_ok) {
        return nullptr;
    }
4.1.3.1 Before setting the js class export, you need to create a life cycle
if (napi_create_reference(env, constructor , 1, &sConstructor_) != napi_ok) {
    return nullptr;
}
  • constructor The data representing the constructor of the class returned when defining the js class
  • sConstructor_ lifecycle variables
4.1.3.1.1 napi_create_reference

napi_create_reference creates a reference for an object to extend its lifetime. The caller needs to manage the reference lifecycle by itself.

napi_create_reference function description:

NAPI_EXTERN napi_status napi_create_reference(napi_env env,
                                              napi_value value,
                                              uint32_t initial_refcount,
                                              napi_ref* result);

Function: Create a new life cycle reference object by referencing the object

  • [in] env: the environment for calling the API
  • [in] value: napi_value indicates the object we want to refer to
  • [in] initial_refcount: The initial reference count of the lifetime variable
  • [out] result: the newly created life cycle reference object
    Return napi_ok This API is successful.
4.1.3.2 Use the life cycle variable as the incoming attribute of the exported object, and export the js class to exports
//  Set the relevant properties of the constructor object and bind it to the export variable exports
if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) !=  napi_ok) {
    return nullptr;
}
4.1.3.2.1 napi_set_named_property

Sets a name for the property of the given object.

napi_status napi_set_named_property(napi_env env,
                                    napi_value object,
                                    const char* utf8Name,
                                    napi_value value);
  • [in] env: the environment for calling the API
  • [in] object: The attribute value to be bound to the related attribute of the NapiTest object
  • [in] utf8Name: the name of the js class
  • [in] value: the object to reference
    If napi_ok is returned, the API is successful
4.1.3.3 Setting properties of exported objects

hello.cpp

    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
4.1.3.3.1 napi_define_properties

https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_nap...

napi_status napi_define_properties(napi_env env,
                                   napi_value object,
                                   size_t property_count,
                                   const napi_property_descriptor* properties);

Function: define attributes to a given Object in batches

  • [in] env: the environment for calling the api
  • [in] object: Export variable of js object related properties
  • [in] property_count: the number of elements in the property array
  • [in] properties: property array

4.1.4 Create an instance object of a class

  • In addition to calling the new method to obtain an instance of a class in the ArkTS application, we can also provide some methods for the ArkTS application to obtain an instance of the corresponding class. For example, in our NapiTest class, a Create method is defined, which implements the NapiTest class instance. Obtain. The specific implementation is as follows:
napi_value NapiTest::Create(napi_env env, napi_callback_info info) {
    napi_status status;
    napi_value constructor = nullptr, result = nullptr;
    // Get lifecycle variables
    status = napi_get_reference_value(env, sConstructor_, &constructor);

    // Create an instance object in the life cycle and return it
    status = napi_new_instance(env, constructor, 0, nullptr, &result);
    auto napiTest = new NapiTest();
    // Bind the instance class to create NapiTest to the exported object result
    if (napi_wrap(env, result, reinterpret_cast<void *>(napiTest), Destructor,
        nullptr, &(napiTest->mRef)) == napi_ok) {
        return result;
    }
    
    return nullptr;
}
  • In the registration of the napi interface, the method is exported as an interface, and the application layer can directly call the interface and obtain the instance pair of the class.
    Special Note: If the method of obtaining a class instance is implemented separately, then the class constructor of js may not be implemented (that is, the code of the actual construction function Constructor and the function Destructor of releasing resources when defining the js structure is sufficient and may not be written)
4.1.4.1 napi_get_reference_value

https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_nap...

Function description:

NAPI_EXTERN napi_status napi_get_reference_value(napi_env env,
                                                 napi_ref ref,
                                                 napi_value* result);
  • Role: Get the js object associated with the reference
  • [in] env: the environment for calling the API
  • [in] ref: variable for lifecycle management
  • [out] result: the reference of the object reference.
4.1.4.2 napi_new_instance

https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_nap...

napi_status napi_new_instance(napi_env env,
                              napi_value cons,
                              size_t argc,
                              napi_value* argv,
                              napi_value* result)
  • Function: Construct an object through a given constructor
  • [in] env: the environment for calling the API
  • [in] cons: napi_value represents the JavaScript function to be called as a constructor
  • [in] argc: the count of elements in the argv array
  • [in] argv: Array of JavaScript values, representing the parameter napi_value of the constructor.
  • [out] result: napi_value indicates the returned JavaScript object

4.2 index.d.ts declaration file writing

Using the NAPI framework code generation tool, .d.ts can be generated from .h
https://gitee.com/openharmony/napi_generator/blob/master/docs...

export const create : () => NapiTest;
export class  NapiTest {
    setMsg(msg: string): void;
    getMsg(): string;
}

can also be written as

export class  NapiTest {
    create();
    setMsg(msg: string): void;
    getMsg(): string;
}

4.3 CMakeLists.txt file

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(ObjectWrapTest)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# header file path
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)
# Dynamic library source file
add_library(entry SHARED hello.cpp NapiTest.cpp)
# Rely on libace_napi.z.so dynamic library
target_link_libraries(entry PUBLIC libace_napi.z.so )

4.4 index.ets file

// Tell the IDE not to check file syntax
// @ts-nocheck 
import testNapi from "libentry.so";

@Entry
@Component

struct Index {
  @State message: string = 'export object'
  @State nativePointer:number = 0

// Create object tt
  tt = testNapi.create();

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            console.info("[NapiTest] Test NAPI 2 + 3 = " + testNapi.add(2, 3));
            try{
              if (this.nativePointer == 0) {
                // log printing, add log in the program
                console.info("[NapiTest] Test NAPI add(2, 3) 1");
                this.nativePointer = testNapi.add(2, 3)
                console.info("[NapiTest] Test NAPI add(2, 3) 2");

                this.tt.setMsg("2+3")
                console.info("[NapiTest] Test NAPI add(2, 3) 3");

              } else {

                console.info("[NapiTest] Test NAPI add(0, 0) 1");

                this.nativePointer = testNapi.add(0, 0)
                console.info("[NapiTest] Test NAPI add(0, 0) 2");

                this.tt.setMsg("4+5")
                console.info("[NapiTest] Test NAPI add(0, 0) 3");
              }
            } catch(e) {
              console.info("[NapiTest]Test NAPI error" + JSON.stringify(e));
            }
            console.info("[NapiTest]Test NAPI " + this.tt.getMsg() + " = " + this.nativePointer);
          })
      }
      .width('100%')
    }
    .height('100%')
  }

}

Knowledge points attached

napi interface name

https://gitee.com/openharmony/docs/blob/master/zh-cn/applicat...

Tags: OpenHarmony

Posted by jimwp on Mon, 13 Mar 2023 18:51:18 +0530