Introduction to the NAPI framework for OpenHarmony
Understanding the NAPI C++ and C framework in order to build third party libraries for OpenHarmony and the developer community that benefits at large.
What is NAPI
The concept of NAPI is derived from Nodejs, and in order to realize the mutual call between javascript scripts and C++ libraries, Nodejs has made a layer of encapsulation of the API of the V8 engine, called NAPI. You can find it on the Nodejs official website (nodejs.org/dist/latest....) to see the various NAPI interface definition descriptions.
As you can see, the NAPI interface itself is implemented in the C++ language, and these interfaces can help C++ code create JS variables, or access JS variables and methods in the JavaScript runtime environment.
NAPI in OpenHarmony
The OpenAtom OpenHarmony (OpenHarmony) application layer is based on the javascript language, while the system framework layer is based on the C++ language. They need a bridge between them to call each other's code, and that bridge is NAPI.
Some of you may have questions here: Are OpenHarmony's NAPI and NodeJs' NAPI the same thing? It should be said that the OpenHarmony system follows the NAPI interface definition form, but the internal implementation of each interface has been rewritten. This is because the nature of the NAPI interface is to help C++ programs interact with the Javascript engine, so different implementations are required for different engines. When the user calls the NAPI interface napi_create_int64(), for Nodejs, it will access the V8 engine's API to create a js numeric variable, and for OpenHarmony, it will access the ArkUI framework's own js engine (ArkNativeEngine). Searching for the napi_create_int64() method in the OpenHarmony source code will give you a header definition: third_party\node\src\js_native_api.h and two different implementations: third_party\node\src\js_native_api_v8.ccfoundation\arkui\napi\ native_engine\native_api.cppnative_api.cpp is the OpenHarmony version of NAPI, and if you want to know the internal details, you can start here:
Create a simple NAPI project
DevEco Studio's Native C++ template allows you to create a sample project with a simple NAPI implementation.
The project comes with a hello.cpp that implements an add() method that can be called by JavaScript code.
Let's take a look at how the NAPI framework works based on this simple example.
How the app calls the NAPI interface
After the application code is imported into the corresponding SO library, you can call the interface implemented by the library.
Here we notice that the name used when importing the logstore is "@ohos.hilog", and if the application code is written as import hilog from 'libhilog.z.so', it can actually be successfully imported. In fact, ArkUI converts @ohos.hilog to a libhilog.z.so at runtime, then looks for the library in the /system/lib/module/ directory and loads it. The NAPI libraries implemented by the system are all located in the /system/lib/module/ directory, similarly: @ohos.wifiManager corresponds to /system/lib/module/libwifimanager.z.so, and @ohos.deviceInfo corresponds to /system/lib//module/libdeviceinfo.z.so
In addition to the NAPI library that comes with the system, applications can also develop their own NAPI libraries in C++. In the example above, import testNapi from 'libentry.so' is implemented by the application itself. The NAPI library for application development will be compiled and packaged into a hap file along with the application project, and finally deployed to the /data directory under each application's own folder.
How the NAPI library is imported
We know that the app's javascript code is interpreted and executed by ArkUI's JS engine. When the JS engine interprets import hilog from '@ohos.hilog'; This line of code will load the corresponding libhilog.z.so into the application process via dlopen(). Each application process initializes an engine instance, ArkNativeEngineImpl, and let's take a look at its constructor foundation\arkui\napi\native_engine\impl\ark\ark_native_engine_impl.cpp
In other words, each application process has a "requireNapi" function registered in the JS engine, and when the application calls this method, the JS engine will handle the loading of the so library through the moduleManager class of the NAPI framework. moduleManager finally found the corresponding so file in /system/lib/module and loaded it into the application process by dlopen(). If you want to know more details, you can read the internal implementation of the NativeModuleManager::LoadNativeModule() method.
There may be a question here: there is no "requireNapi" code in the javascript code of the application, only import xxx, how to trigger the import handler? The answer has to be found in the compiled js code. Let's unpack the compiled hap package and find the js file corresponding to the ets file:
As you can see, after index.ets is compiled to index.js, the import keyword is also changed to "requireNapi", so that the JS engine will call the registered import handler when executing this line of code.
How C++ libraries implement JS methods
Now that JS has introduced the C++ library, the next step is how JS calls the methods in the C++ library. Let's start with the conclusion: whether a C++ method can be called by an application depends on whether the C++ code registers the method with the JS engine.
Let's take a look at how hello.cpp registers the add method:
Let's look at this code from below: First, the RegisterEntryModule(void) method. This is the starting code for C++ to register NAPI modules and methods with the JS engine. Note that this method is preceded by a compilation modifier "attribute((constructor))", which is used to guide the compilation of C++ code, so that when the so library is loaded into the application process, the RegisterEntryModule(void) method will be automatically called. The method registers a napi_module with the JS engine via the NAPI interface napi_module_register().
Then there's the Init() method. This method implements the registration of the Add method. That is, tell the JS engine to map the JS symbol "add" with the C++ method "add". In this way, when the JS engine interprets the execution of the javascript code "testNapi.add(2, 3)", it will find the function address of the C++ Add() method and call it. As shown in the figure below:
The problem of method association calls has also been solved, and finally the JS runtime and C++ runtime environment have been switched to each other. When the Add method of C++ is called by the JS engine, the engine will pass the parameter variables issued by javascript to C++. All variables passed from the JS runtime are represented by napi_value types. You need to convert to a C++ variable type through the NAPI interface. See the comments for each line of code in the figure below:
napi_value is not a specific type, it is similar to void*, which represents the address of a JS variable in the JS engine's internal storage. It needs to be implemented through the corresponding NAPI method, for example: napi_get_value_int32() --- js variable to c++ integer napi_get_value_string_utf8() --- js variable to c++ string napi_get_value_bool() --- js variable to c++ boolean
For the specific usage and usage scenarios of these interfaces, please refer to the official NodeJs documentation (nodejs.org/dist/latest....)
C++ programs link to the NAPI library
OpenHarmony's NAPI interface implementations are all encapsulated in libace_napi.z.so, which C++ programs need to link to when compiling. For the cpp code developed by the DevEco Studio app, link it in the corresponding CMakeLists.txt. The library file can be found in the SDK directory.
For device-side development, C++ programs in the system framework define dependencies through BUILD.gn files.
summary
NAPI is the bridge between JavaScript and C++. In OpenHarmony, Javascript code is interpreted and executed by ArkUI's JS engine at runtime, and C++ code accesses the Javascript context in the JS engine through the NAPI interface, enabling mutual calls to JS variables and methods.
Reference Links
The following is the address of the source code repository
Original article, translated from China, Juejin blog platform: Introduction to the NAPI framework for OpenHarmony
Zhang Zhicheng
Senior technical expert of ArcherMind Technology (OpenHarmony developer)