
??想了解更多關(guān)于開源的內(nèi)容,請訪問:??
??51CTO 開源基礎(chǔ)軟件社區(qū)??
??https://ost.51cto.com??
NAPI是什么?
NAPI(Native API)組件是一套對外接口基于Node.js N-API規(guī)范開發(fā)的原生模塊擴展開發(fā)框架。
在移動應(yīng)用開發(fā)中需要使用 C/C++ 實現(xiàn)的場景有很多,比如音視頻處理,圖像處理等較高性能要求的場景。
OpenHarmony 提供了 NAPI 框架用于實現(xiàn) JS 和 C/C++ 互相調(diào)用的能力,DevEco Studio 默認(rèn)支持創(chuàng)建 NAPI 應(yīng)用,我們今天就來創(chuàng)建一個NAPI 工程。
創(chuàng)建NAPI 工程
打開IDE,選擇創(chuàng)建項目,選擇下面的Native C++模板。

點擊 Next 按鈕后,項目名稱選擇NapiHello,點擊finish即可。

等待項目加載完成,如果大家觀察細(xì)致的話,會發(fā)現(xiàn),這里面有個不同之處就是多了CPP目錄。

我么可以點開來看一下。

該目錄用來存放 cpp 的源碼及相關(guān)配置文件,各文件說明如下:
- hello.cpp:index.d.ts 文件中聲明的方法的 C++ 實現(xiàn)源碼。
- CMakeLists.txt:是cmake用來生成Makefile文件需要的一個描述編譯鏈接的腳本文件。
- index.d.ts:對 ts 提供的方法聲明。
- package.json:打包的配置文件。
另外 CMakeLists.txt 文件還會在 build-profile.json5 里做配置,代碼如下所示:
{
"apiType": 'stageMode',
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
}
},
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}
點擊自動化簽名,然后運行項目就可以。

這個時候我們就可以來看一下代碼內(nèi)容,分析一下了。
我們再來看一下index.ets的內(nèi)容。
import hilog from '@ohos.hilog';
import testNapi from 'libentry.so'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.message = "值為"+testNapi.add(2, 3)
hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
})
}
.width('100%')
}
.height('100%')
}
}
運行之后,我們就可以看到。
NAPI項目簡述
index.d.ts解讀
在 cpp 的 libentry 目錄下生成了 index.d.ts 文件,它的源碼如下所示:
export const add: (a: number, b: number) => number;
export const 表示導(dǎo)出一個常量以便在其它文件中使用。add 是一個返回類型為 ??number?
?? 的方法,它的參數(shù)類為 ??number?
? 類型。
package.json解讀
在 cpp 的 libentry 目錄下生成了 package.json 文件,該文件是打包的配置文件,內(nèi)容如下所示:
{
"name": "libentry.so",
"types": "./index.d.ts"
}
設(shè)置 libentry.so 庫和 index.d.ts 相關(guān)聯(lián),便于在 TS 文件中引入 libentry.so 時調(diào)用庫中的相關(guān)方法。
CMakeLists.txt解讀
CMake 是一個開源跨平臺的構(gòu)建工具,旨在構(gòu)建、測試和打包軟件,CMake 是 makefile 的上層工具,用于跨平臺構(gòu)建環(huán)境,生成可移植的 makefile 并簡化自動動手寫 makefile 的工作量,在 cpp 目錄下默認(rèn)生成的 CMakeLists.txt 內(nèi)容如下所示:
# the minimum version of CMake.
# 聲明使用 CMAKE 的最小版本號
cmake_minimum_required(VERSION 3.4.1)
# 聲明項目的名稱
project(oh_0400_napi)
# set命令,格式為set(key value),表示設(shè)置key的值為value,其中value可以是路徑,也可以是許多文件。
# 本例中設(shè)置NATIVERENDER_ROOT_PATH的值為${CMAKE_CURRENT_SOURCE_DIR}
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 添加項目編譯所需要的頭文件的目錄
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
# 生成目標(biāo)庫文件libentry.so,entry表示最終的庫名稱,SHARED表示生成的是動態(tài)鏈接庫,
# hello.cpp表示最終生成的libentry.so中所包含的源碼
# 如果要生成靜態(tài)鏈接庫,把SHARED該成STATIC即可
add_library(entry SHARED hello.cpp)
# 把libentry.so鏈接到libace_napi.z.so上
target_link_libraries(entry PUBLIC libace_napi.z.so)
hello.cpp解讀
在 cpp 目錄下默認(rèn)生成的 hello.cpp 文件,源碼如下所示:
#include "napi/native_api.h"
#include <js_native_api.h>
#include <js_native_api_types.h>
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_get_cb_info(env, info, &argc, args , nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
napi_value sum;
napi_create_double(env, value0 + value1, &sum);
return sum;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
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;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version =1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&demoModule);
}
hello.cpp 的代碼不是很復(fù)雜,我們可以做如下拆分:
#include "napi/native_api.h"
#include <js_native_api.h>
#include <js_native_api_types.h>
引入頭文件,作用和 TS 里的 import 類似,不再詳述。
static napi_module demoModule = {
.nm_version =1,//nm_version:nm版本號,默認(rèn)值為 1。
.nm_flags = 0,//nm標(biāo)記符,默認(rèn)值為 0。
.nm_filename = nullptr,//暫不關(guān)注,使用默認(rèn)值即可。
.nm_register_func = Init,//指定nm的入口函數(shù)。
.nm_modname = "entry",//指定 TS 頁面導(dǎo)入的模塊名,例如:`import testNapi from 'libentry.so'` 中的 testNapi
//就是當(dāng)前的nm_modname。
.nm_priv = ((void*)0),//暫不關(guān)注,使用默認(rèn)值即可。
.reserved = { 0 },//暫不關(guān)注,使用默認(rèn)值即可。
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&demoModule);
}
定義 NAPI 模塊,類型為 napi_module 結(jié)構(gòu)體,各字段說明如下:
- nm_version:nm版本號,默認(rèn)值為 1。
- nm_flags:nm標(biāo)記符,默認(rèn)值為 0。
- nm_filename:暫不關(guān)注,使用默認(rèn)值即可。
- nm_register_func:指定nm的入口函數(shù)。
- nm_modname:指定 TS 頁面導(dǎo)入的模塊名,例如:import testNapi from 'libentry.so' 中的 testNapi 就是當(dāng)前的nm_modname。
- nm_priv:暫不關(guān)注,使用默認(rèn)值即可。
- reserved:暫不關(guān)注,使用默認(rèn)值即可。
extern "C" 簡單理解就是告訴編譯器這部分代碼按照 C 語言進(jìn)行編譯而不是 C++ 語言編譯。__attribute__((constructor)) 聲明方法的執(zhí)行時機,它表示 RegisterEntryModule() 方法在 main() 方法執(zhí)行前執(zhí)行, RegisterEntryModule() 方法內(nèi)調(diào)用了 napi_module_register() 方法,該方法是 NAPI 提供的模塊注冊方法,表示把定義的 demoModule 模塊注冊到系統(tǒng)中。
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
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;
}
EXTERN_C_END
Init() 方法內(nèi)聲明了 napi_property_descriptor 結(jié)構(gòu)體,結(jié)構(gòu)體的定義看第一個和第三個參數(shù)即可,第一個參數(shù) add 表示應(yīng)用層 JS 聲明的方法,Add 表示 Native C++ 實現(xiàn)的方法,然后調(diào)用 NAPI 的 napi_define_properties() 方法將 add 和 Add 做個映射,最后通過 exports 變量對外導(dǎo)出,實現(xiàn) JS 端調(diào)用 add 方法時進(jìn)而調(diào)用到 C++ 的 Add() 方法。
static napi_value Add(napi_env env, napi_callback_info info)
{
// 獲取 2 個參數(shù),napi_value是對 JS 類型的封裝
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
// 調(diào)用napi_get_cb_info方法,從 info 中讀取傳遞進(jìn)來的參數(shù)放入args里
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
// 獲取參數(shù)并校驗類型
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
// 調(diào)用napi_get_value_double把 napi_value 類型轉(zhuǎn)換成 C++ 的 double 類型
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
// 調(diào)用napi_create_double方法把 C++類型轉(zhuǎn)換成 napi_value 類型
napi_value sum;
napi_create_double(env, value0 + value1, &sum);
// 返回 napi_value 類型
return sum;
}
Add() 方法注釋的很清楚,首先從 napi_callback_info 中讀取 napi_value 類型的參數(shù)放入到 args 中,然后從 args 中讀取參數(shù)并把 napi_value 類型轉(zhuǎn)換成 C++ 類型后進(jìn)行加操作,最后把相加的結(jié)果轉(zhuǎn)換成 napi_value 類型并返回。
import testNapi from 'libentry.so'
根據(jù)前邊的編譯配置,cpp 目錄下的源碼最終打包成了 libentry.so,使用前直接引入即可。
import hilog from '@ohos.hilog';
import testNapi from 'libentry.so'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.message = "值為"+testNapi.add(2, 3)
hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
})
}
.width('100%')
}
.height('100%')
}
}
引入 libentry.so 模塊后,就可以直接調(diào)用 ??add()?
? 方法了。
NAPI數(shù)據(jù)類型
napi_value數(shù)據(jù)類型
OpenHarmony NAPI 將 ECMAScript 標(biāo)準(zhǔn)中定義的 Boolean、Null、Undefined、Number、BigInt、String、Symbol和 Object 這八種數(shù)據(jù)類型以及函數(shù)對應(yīng)的 Function 類型統(tǒng)一封裝成了 napi_value 類型,它是 JS 數(shù)據(jù)類型和 C/C++ 數(shù)據(jù)類型之間的橋梁,??napi_value (opens new window)??官網(wǎng)說明如下:
napi_value 表示 JS 值的不透明指針,在 C/C++ 端要使用 JS 端傳遞的數(shù)據(jù)類型,都是通過 NAPI 提供的相關(guān)方法把napi_value轉(zhuǎn)換成 C/C++ 類型后再使用,同理當(dāng)需要把 C/C的數(shù)據(jù)傳遞給 JS 應(yīng)用層也要通過 NAPI 提供的方法把 C/C 端的數(shù)據(jù)轉(zhuǎn)換成 napi_value 再向上傳遞。

C/C++轉(zhuǎn)napi_value
NAPI提供了 napi_create_ 開頭的方法表示把 C/C++ 類型轉(zhuǎn)換成 napi_value 類型,常見方法如下所示:
int類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_create_int32(napi_env env,
int32_t value,
napi_value* result);
NAPI_EXTERN napi_status napi_create_uint32(napi_env env,
uint32_t value,
napi_value* result);
NAPI_EXTERN napi_status napi_create_int64(napi_env env,
int64_t value,
napi_value* result);
把 C/C++ 的 int32_t、uint32_t 以及 int64_t 類型轉(zhuǎn)換成 napi_value 類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- value:C/C++端的 int 類型的值。
- result:napi_value,返回給 JS 應(yīng)用層的數(shù)據(jù)。
double類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_create_double(napi_env env,
double value,
napi_value* result);
把 C/C++ 端的 double 類型轉(zhuǎn)換成 napi_value 類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- value:C/C++ 端的 double 類型的值。
- result:napi_value,返回給 JS 應(yīng)用層的數(shù)據(jù)。
string類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env,
const char* str,
size_t length,
napi_value* result);
NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env,
const char* str,
size_t length,
napi_value* result);
NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env,
const char16_t* str,
size_t length,
napi_value* result);
把 C/C++ 端的 char 類型轉(zhuǎn)換成 napi_value 類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- str:C/C++端的字符串類型的值。
- size_t:str 的長度。
- result:napi_value,返回給 JS 應(yīng)用層的數(shù)據(jù)。
napi_value轉(zhuǎn)C/C++
NAPI提供了 napi_get_value_ 開頭的方法表示把 napi_value 轉(zhuǎn)換成 C/C++ 類型,常見方法如下所示:
int類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_get_value_int32(napi_env env,
napi_value value,
int32_t* result);
NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env,
napi_value value,
uint32_t* result);
NAPI_EXTERN napi_status napi_get_value_int64(napi_env env,
napi_value value,
int64_t* result);
把 JS 端的 number 類型轉(zhuǎn)換成 C/C++ 的對應(yīng)數(shù)據(jù)類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- value:JS 端傳遞進(jìn)來的數(shù)據(jù)。
- result:接收 value 的值。
double類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_get_value_double(napi_env env,
napi_value value,
double* result);
把 JS 端的 number 類型轉(zhuǎn)換成 C/C++ 的 double 類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- value:JS 端傳遞進(jìn)來的數(shù)據(jù)。
- result:接收 value 的值。
string類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env,
napi_value value,
char* buf,
size_t bufsize,
size_t* result);
// Copies UTF-8 encoded bytes from a string into a buffer.
NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env,
napi_value value,
char* buf,
size_t bufsize,
size_t* result);
// Copies UTF-16 encoded bytes from a string into a buffer.
NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env,
napi_value value,
char16_t* buf,
size_t bufsize,
size_t* result);
把 JS 端的 string 類型轉(zhuǎn)換成 C/C++ 的 char 類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- value:napi_value,JS 端傳遞進(jìn)來的數(shù)據(jù)。
- buf:char數(shù)組,用來存放napi_value中的 string 值
- bufsize:char數(shù)組長度
- result:接收 value 的值。
boolean類型轉(zhuǎn)換
NAPI_EXTERN napi_status napi_get_value_bool(napi_env env,
napi_value value,
bool* result);
把 JS 端的 boolean 類型轉(zhuǎn)換成 C/C++ 的 bool 類型,參數(shù)說明如下:
- env:方法調(diào)用者的運行環(huán)境,包含 JS 引擎等。
- value:JS 端傳遞進(jìn)來的數(shù)據(jù)。
- result:接收 value 的值。
參考
Node-API:
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/native-lib/third_party_napi/napi.md/。
原生模塊擴展開發(fā)框架:
https://gitee.com/openharmony/arkui_napi。
Node_API :用于封裝JavaScript能力為native插件的API,獨立于底層JavaScript,并作為Node.js的一部分。
??Native API中支持的標(biāo)準(zhǔn)庫?? :目前支持標(biāo)準(zhǔn)C庫、C++庫、OpenSL ES、zlib。
C常用函數(shù)庫 :math.h。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/sin。
Cmake :管理源代碼構(gòu)建的工具。
??想了解更多關(guān)于開源的內(nèi)容,請訪問:??
??51CTO 開源基礎(chǔ)軟件社區(qū)??
??https://ost.51cto.com??