通過本文您將熟悉:
- 如何注冊(cè)NAPI模塊及接口。
- 如何在ArkUI eTS代碼中調(diào)用擴(kuò)展的NAPI接口。
- full-SDK的替換。
什么是NAPI
NAPI(Native API)組件是一套對(duì)外接口基于Node.js N-API規(guī)范開發(fā)的原生模塊擴(kuò)展開發(fā)框架。
OpenHarmony中組件有一種是C和C++語言的三方組件,通常以源碼或OpenHarmony hpm包的方式引入,在應(yīng)用開發(fā)中以NAPI的方式使用,或直接編譯在OpenHarmony操作系統(tǒng)鏡像

NAPI組件架構(gòu)圖
-
OpenHarmony 標(biāo)準(zhǔn)系統(tǒng)應(yīng)用開發(fā)基于ArkUI框架,開發(fā)語言使用JS/eTS。部分業(yè)務(wù)場景依賴使用現(xiàn)有的C/C++ 庫,或?yàn)榱双@取更高的性能。OpenHarmony提供NAPI機(jī)制,用于規(guī)范封裝IO、CPU密集型、OS底層等能力并對(duì)外暴露JS接口,通過NAPI實(shí)現(xiàn)JS和C/C++代碼的互相訪問.
- 重點(diǎn)講解了NAPI接口如何實(shí)現(xiàn)OpenCV以及SeetaFace的調(diào)用。一句話概括就是,鐘祿平和林嘉誠老師講述了移植了三方庫后通過NAPI將庫的C/C++接口變成JS/ETS接口給應(yīng)用層調(diào)用。
OpenHarmony 中的 N-API 定義了由 JS/ETS 語言編寫的代碼和 native 代碼(使用 C/C++ 編寫)交互的方式,由 Node.js N-API 框架擴(kuò)展而來。
- N-API:Native Application Programming Interface(本地應(yīng)用程序接接口)
- 什么是Node.js N-API 框架
Node.js N-API為開發(fā)者提供了一套C/C++ API用于開發(fā)Node.js的Native擴(kuò)展模塊。從Node.js 8.0.0開始,N-API以實(shí)驗(yàn)性特性作為Node.js本身的一部分被引入,并且從Node.js 10.0.0開始正式全面支持N-API。
添加OpenHarmony自定義子系統(tǒng)、組件、模塊
- 這部分內(nèi)容涉及三方庫移植,為便于本篇NAPI基礎(chǔ)的學(xué)習(xí)。筆者在此自定義一個(gè)子系統(tǒng)用于開發(fā)NAPI。如在已存在的子系統(tǒng)組件中添加擴(kuò)展NAPI,則跳過此步。
- 需要準(zhǔn)備好OpenHarmonyBeta3源碼和編譯環(huán)境
添加子系統(tǒng)、組件
直接在OpenHarmony源碼根目錄創(chuàng)建子系統(tǒng)文件夾,取名mysubsys。并在目錄下添加子系統(tǒng)的構(gòu)建配置文件ohos.build
完整內(nèi)容如下:
{
"subsystem": "mysubsys",
"parts": {
"hello": {
"module_list": [
"http://mysubsys/hello/hellonapi:hellonapi"
],
"inner_kits": [
],
"system_kits": [
],
"test_list": [
]
}
}
}
- 另外ohos.build里面不支持加注釋,后面編譯的時(shí)候會(huì)莫名其妙報(bào)錯(cuò)。別問,問就是筆者踩過坑了。(好像也沒必要加注釋)
需要明白以下知識(shí)點(diǎn):
"subsystem": "mysubsys",
- subsystem后面的mysubsy是子系統(tǒng)的名稱。
"parts": {
"hello": {
}
}
- hello是組件名稱,被mysubsys子系統(tǒng)包含
"module_list": [
"http://mysubsys/hello/hellonapi:hellonapi"
- hellonapi是模塊名,被hello組件包含。
接著將子系統(tǒng)配置到源碼下build\subsystem_config.json文件,在該文件中插入如下內(nèi)容。
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys",
"dir": ""
}
- OpenHarmony系統(tǒng)架構(gòu)中,子系統(tǒng)是一個(gè)邏輯概念,它具體由對(duì)應(yīng)的組件構(gòu)成。組件是對(duì)子系統(tǒng)的進(jìn)一步拆分,可復(fù)用的軟件單元,它包含源碼、配置文件、資源文件和編譯腳本;能獨(dú)立構(gòu)建,以二進(jìn)制方式集成,具備獨(dú)立驗(yàn)證能力的二進(jìn)制單元。
本示例按子系統(tǒng)system > 組件part > 組件module 創(chuàng)建了3級(jí)目錄
mysubsys -- 子系統(tǒng)目錄
├── hello -- 組件目錄
│ └── hellonapi
│ ├── BUILD.gn -- 組件module目錄
│ └── hellonapi.cpp
└── ohos.build
源碼實(shí)現(xiàn)
最后在組件目錄下中創(chuàng)建代碼文件hellonapi.cpp
完整內(nèi)容如下:
#include <string.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
// 接口業(yè)務(wù)實(shí)現(xiàn)C/C++代碼
// std::string 需要引入string頭文件,#include <string>
// 該napi_module對(duì)外具體的提供的API接口是 getHelloString
static napi_value getHelloString(napi_env env, napi_callback_info info) {
napi_value result;
std::string words = "Hello OpenHarmony NAPI";
NAPI_CALL(env, napi_create_string_utf8(env, words.c_str(), words.length(), &result));
return result;
}
// 注冊(cè)對(duì)外接口的處理函數(shù)napi_addon_register_func
// 2.指定NAPI模塊注冊(cè)對(duì)外接口的處理函數(shù),具體擴(kuò)展的接口在該函數(shù)中聲明
// 模塊對(duì)外接口注冊(cè)函數(shù)為registerFunc
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
// 聲明該napi_module對(duì)外具體的提供的API為getHelloString
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
// 注冊(cè)NAPI模塊
// 1.先定義NAPI模塊,指定當(dāng)前NAPI模塊對(duì)應(yīng)的模塊名
// 以及模塊注冊(cè)對(duì)外接口的處理函數(shù),具體擴(kuò)展的接口在該函數(shù)中聲明
// nm_modname: NAPI模塊名稱,對(duì)應(yīng)eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
// 示例對(duì)應(yīng)hap應(yīng)用中eTS代碼需要包含import hellonapi from '@ohos.hellonapi'
// 以下的出現(xiàn)的hellonapi都為注冊(cè)的NAPI模塊名
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
// registerFunc是NAPI模塊對(duì)外接口注冊(cè)函數(shù)
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
// 3.NAPI模塊定義好后,調(diào)用NAPI提供的模塊注冊(cè)函數(shù)napi_module_register(napi_module* mod)函數(shù)注冊(cè)到系統(tǒng)中。
// register module,設(shè)備啟動(dòng)時(shí)自動(dòng)調(diào)用此constructor函數(shù),把定義的模塊注冊(cè)到OpenHarmony中。
// 以下出現(xiàn)的hellonapi都是注冊(cè)的NAPI模塊名
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
// napi_module_register是ohos的NAPI組件提供的模塊注冊(cè)函數(shù)
napi_module_register(&hellonapiModule);
}
代碼解析如下:
接口業(yè)務(wù)實(shí)現(xiàn)C/C++代碼
// 接口業(yè)務(wù)實(shí)現(xiàn)C/C++代碼
// std::string 需要引入string頭文件,#include <string>
// 該napi_module對(duì)外具體的提供的API接口是 getHelloString
static napi_value getHelloString(napi_env env, napi_callback_info info) {
napi_value result;
std::string words = "Hello OpenHarmony NAPI";
NAPI_CALL(env, napi_create_string_utf8(env, words.c_str(), words.length(), &result));
return result;
}
添加NAPI接口頭文件
NAPI提供了提供了一系列接口函數(shù),聲明包含如下2個(gè)頭文件中,先添加這2個(gè)頭文件到hellonapi.cpp
#include "napi/native_api.h"
#include "napi/native_node_api.h"
- native_api.h和native_node_api.h這兩個(gè)頭文件
- 在OpenHarmony3.1release源碼中在//foundation/ace/napi/interfaces/kits目錄下
- 在OpenHarmony3.2beta3源碼中分別在//foundation/arkui/napi/interfaces/kits和//foundation/arkui/napi/interfaces/inner_api目錄下了。

注冊(cè)NAPI模塊、添加接口聲明
定義的hellonapi模塊,其對(duì)應(yīng)結(jié)構(gòu)體為napi_module。
- 指定當(dāng)前NAPI模塊對(duì)應(yīng)的模塊名
- 模塊注冊(cè)對(duì)外接口的處理函數(shù),具體擴(kuò)展的接口在該函數(shù)中聲明。
// 注冊(cè)對(duì)外接口的處理函數(shù)napi_addon_register_func
// 2.指定NAPI模塊注冊(cè)對(duì)外接口的處理函數(shù),具體擴(kuò)展的接口在該函數(shù)中聲明
// 模塊對(duì)外接口注冊(cè)函數(shù)為registerFunc
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
// 聲明該napi_module對(duì)外具體的提供的API為getHelloString
// 在napi_property_descriptor desc[]中需要將編寫C語言的“getHelloString”方法與對(duì)暴露的Js語言的接口“getHelloString”進(jìn)行關(guān)聯(lián)。
/*
NAPI提供DECLARE_NAPI_FUNCTION(name, func)函數(shù)用于聲明api,
傳入名稱和其他實(shí)現(xiàn)函數(shù)。在registerFunc函數(shù)中添加DECLARE_NAPI_FUNCTION
*/
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
// 注冊(cè)NAPI模塊
// 1.先定義NAPI模塊,指定當(dāng)前NAPI模塊對(duì)應(yīng)的模塊名
// 以及模塊注冊(cè)對(duì)外接口的處理函數(shù),具體擴(kuò)展的接口在該函數(shù)中聲明
// nm_modname: NAPI模塊名稱,對(duì)應(yīng)eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
// 示例對(duì)應(yīng)hap應(yīng)用中eTS代碼需要包含import hellonapi from '@ohos.hellonapi'
// 以下的出現(xiàn)的hellonapi都為注冊(cè)的NAPI模塊名
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
// registerFunc是該自定義的NAPI模塊對(duì)外接口注冊(cè)函數(shù)
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
// 3.NAPI模塊定義好后,調(diào)用ohos的NAPI組件提供的模塊注冊(cè)函數(shù)napi_module_register(napi_module* mod)函數(shù)注冊(cè)到系統(tǒng)中。
// register module,設(shè)備啟動(dòng)時(shí)自動(dòng)調(diào)用constructor函數(shù),把定義的模塊注冊(cè)到OpenHarmony中。
// 以下出現(xiàn)的hellonapi都是注冊(cè)的NAPI模塊名
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
// napi_module_register(napi_module* mod)是ohos的NAPI組件提供的模塊注冊(cè)函數(shù)
napi_module_register(&hellonapiModule);
}
-
napi_module_register(napi_module* mod)是ohos的NAPI組件提供的模塊注冊(cè)函數(shù)。
- 該函數(shù)在源碼目錄下foundation/arkui/napi/native_engine/native_node.cpp

注冊(cè)NAPI模塊總結(jié)

自定義子系統(tǒng)構(gòu)建
hellonapi編譯gn化,新增gn工程構(gòu)建腳本。
在模塊hellonapi目錄下新建BUILD.gn文件,內(nèi)容如下:
gn文件支持注釋,以
#開頭
import("http://build/ohos.gni")
#ohos_shared_library()中的hellonapi決定了生成動(dòng)態(tài)庫的名稱,增量編譯階段生成動(dòng)態(tài)庫libhellonapi.z.so
ohos_shared_library("hellonapi") {
include_dirs = [
#NAPI頭文件目錄
"http://foundation/arkui/napi/interfaces/kits",
"http://foundation/arkui/napi/interfaces/inner_api",
#根據(jù)增量編譯階段報(bào)錯(cuò)添加的頭文件目錄
"http://third_party/node/src"
]
#根據(jù)增量編譯時(shí)clang編譯器報(bào)警,添加的cflag
cflags_cc = [
#編譯時(shí)報(bào)錯(cuò)提示"-Werror",則加上"-Wno-error"
"-Wno-error",
#編譯時(shí)報(bào)錯(cuò)提示"-Wunused-function",則加上"-Wno-unused-function"
"-Wno-unused-function",
]
#編譯需要的源文件
sources = [
"hellonapi.cpp"
]
#指定編譯依賴libace_napi.z.so動(dòng)態(tài)庫
deps = [ "http://foundation/arkui/napi:ace_napi" ]
#指定庫生成的路徑
#libhellonapi.z.so會(huì)安裝在rk3568開發(fā)板的system/lib/module目錄下
relative_install_dir = "module"
#子系統(tǒng)名稱是mysubsys
subsystem_name = "mysubsys"
#組件名稱是hello
part_name = "hello"
}
修改產(chǎn)品配置
將組件添加到需要的產(chǎn)品配置文件,源碼目錄下的productdefine/common/products/ohos-arm64.json。
- 插入位置任意,但要注意行尾的逗號(hào),確保格式j(luò)son文件格式正確。
"parts":{
...
"mysubsys:hello":{},
...
}
- mysubsys是本示例自定義的子系統(tǒng)名稱
- hello是自定義子系統(tǒng)下的組件名稱
- parts格式如下:
"parts":{
"部件所屬子系統(tǒng)名:部件名":{}
}
修改build/subsystem_config.json
新增子系統(tǒng)定義。
- subsystem_config.json文件定義了有哪些子系統(tǒng)以及這些子系統(tǒng)所在文件夾路徑,添加子系統(tǒng)時(shí)需要說明子系統(tǒng)path與name,分別表示子系統(tǒng)路徑和子系統(tǒng)名。
注意json文件也不支持注釋!?。?/p>
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys"
}
-
"path": "mysubsys",表示子系統(tǒng)路徑 -
"name": "mysubsys"表示子系統(tǒng)名稱
修改vendor/hihope/rk3568/config.json文件
將mysubsys子系統(tǒng)添加至rk3568開發(fā)板,在vendor目錄下新增產(chǎn)品的定義。
{
"subsystem": "mysubsys",
"components": [
{
"component": "hello",
"features": []
}
]
}
-
"subsystem": "mysubsys",表示添加的子系統(tǒng)是mysubsys -
"component": "hello",表示添加的子系統(tǒng)中包含的組件名稱是hello
編譯燒錄
先進(jìn)行增量編譯出子系統(tǒng)的動(dòng)態(tài)庫,增量編譯沒有報(bào)錯(cuò)后。再全量編譯出鏡像,將其燒錄到開發(fā)板上。
- 增量編譯命令
./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64

- 全量編譯和燒錄
鏡像文件在源碼目錄下位置如下:

調(diào)用接口
full-SDK替換(可選)
從OpenHarmony 3.2 Beta2起,SDK會(huì)同時(shí)提供Public SDK和Full SDK。通過DevEco Studio默認(rèn)獲取的SDK為Public SDK。
兩者差異如下:
- Public SDK
- 面向應(yīng)用開發(fā)者提供,不包含需要使用系統(tǒng)權(quán)限的系統(tǒng)接口。通過DevEco Studio默認(rèn)獲取的SDK為Public SDK。
- Full SDK
- 面向OEM廠商提供,包含了需要使用系統(tǒng)權(quán)限的系統(tǒng)接口。使用Full SDK時(shí)需要手動(dòng)從鏡像站點(diǎn)獲取,并在DevEco Studio中替換
筆者使用的DevEco Studio版本為3.0.0.993,即DevEco Studio 3.0。API為API9。


若提示找不到npm,需要配置一下環(huán)境變量,將以下路徑添加到環(huán)境變量中即可


D:\DevEco Studio\ohos\sdk\ets\build-tools\ets-loader

創(chuàng)建OpenHarmony標(biāo)準(zhǔn)應(yīng)用
新建項(xiàng)目,選擇OpenHarmony。

compile sdk選擇9,其他保持默認(rèn)即可。

調(diào)用接口
第一步:調(diào)用方式和ArkUI框架提供的API一樣,先import引入擴(kuò)展的NAPI模塊,后直接調(diào)用。

index.ets內(nèi)容如下:
import prompt from '@system.prompt'
//顯示文本彈窗
// 引入擴(kuò)展的NAPI模塊
// 在hellonapi.cpp文件中定義nm_modname(模塊名稱)為hellonapi
// 在BUILD.gn文件中定義ohos_shared_library結(jié)構(gòu)體名稱為hellonapi
// 所以是import hellonapi from '@ohos.hellonapi'
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
struct HelloNAPI {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("NAPI: hellonapi.getHelloString()").margin(10).fontSize(24).onClick(() => {
// hellonapi.cpp對(duì)外具體的提供的API是getHelloString
let strFromNAPI = hellonapi.getHelloString()
prompt.showToast({ message: strFromNAPI })
})
}
.width('100%')
.height('100%')
}
}
第二步(可選):參考其他模塊的.d.ts創(chuàng)建擴(kuò)展模塊@ohos.hellonapi.d.ts定義文件,放到IDE安裝OpenHarmony SDK的目錄路徑ohos\sdk\ets\3.2.7.5\api下。
- .d.ts文件的命名為@ohos.ohos_shared_library_name.d.ts,ohos_shared_library為BUID.gn文件中定義的動(dòng)態(tài)庫名稱

@ohos.hellonapi.d.ts內(nèi)容如下:
declare namespace hellonapi {
function getHelloString(): string;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;
-
@since 9表示API的版本為9 -
@syscap SystemCapability.Ability.AbilityRuntime.AbilityCore語句在.d.ts文件中一定要添加,否則IDE還是會(huì)報(bào)錯(cuò)找不到該文件。 -
declare namespace hellonapi和export default hellonapi的hellonapi是BUILD.gn中的定義的ohos_shared_library_name。 -
function getHelloString(): string;中的getHelloString()是hellonapi.cpp文件中指定的模塊注冊(cè)對(duì)外接口的處理函數(shù)
則IDE掃描如下:

標(biāo)準(zhǔn)應(yīng)用編譯不是強(qiáng)依賴OpenHarmony SDK,所以可忽略IDE中告警,直接編譯打包hap。但是有的時(shí)候IDE會(huì)提示找不到@ohos.hellonapi.d.ts,然后有小概率的機(jī)會(huì)無法安裝hap。這個(gè)時(shí)候就要參考o(jì)hos\sdk\ets\3.2.7.5\api下的.d.ts文件編寫@ohos.hellonapi.d.ts了。
如果不新建@ohos.hellonapi.d.ts放在sdk\ets\3.2.7.5\api,則IDE會(huì)報(bào)錯(cuò)

第三步:選擇自動(dòng)簽名


第四步:將應(yīng)用安裝到dayu200開發(fā)板上

運(yùn)行效果如下:

總結(jié)思考
- 在本篇文章中添加的子系統(tǒng)目錄下mysubsys/hello/hellonapi/BUILD.gn文件中有下圖中這句代碼,代表編譯時(shí)mysubsys子系統(tǒng)生成的libhellonapi.z.so依賴編譯出的foundation子系統(tǒng)下的arkui部件下的napi組件生成的libace_napi.z.so。

可以查閱ohos3.2beta3源碼(筆者在撰寫此篇文章時(shí)使用的是這個(gè)版本的源碼)foundation/arkui/napi下的BUILD.gn文件驗(yàn)證

分析上圖可以知道編譯foundation子系統(tǒng)下的arkui部件下的napi組件生成libace_napi.z.so動(dòng)態(tài)庫。
知識(shí)點(diǎn)附送
Native API中支持的標(biāo)準(zhǔn)庫
表1 OpenHarmony支持的標(biāo)準(zhǔn)庫
| 名稱 | 簡介 |
|---|---|
| 標(biāo)準(zhǔn)C庫 | libc、libm、libdl 組合實(shí)現(xiàn)C11標(biāo)準(zhǔn)C庫。 |
| 標(biāo)準(zhǔn)C++庫 | libc++ 是C++標(biāo)準(zhǔn)庫的一種實(shí)現(xiàn)。 |
| OpenSL ES | OpenSL ES 是一個(gè)嵌入式跨平臺(tái)的音頻處理庫。 |
| zlib | Zlib 是基于C/C++語言實(shí)現(xiàn)的一個(gè)通用的數(shù)據(jù)壓縮庫。 |
| EGL | EGL是渲染API與底層原生窗口系統(tǒng)之間的一種標(biāo)準(zhǔn)的軟件接口。 |
| OpenGL ES | OpenGL ES 是一個(gè)嵌入式跨平臺(tái)的為 3D 圖形處理硬件指定標(biāo)準(zhǔn)的軟件接口。 |
寫在最后
如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助,我想邀請(qǐng)你幫我三個(gè)小忙:
- 點(diǎn)贊,轉(zhuǎn)發(fā),有你們的 『點(diǎn)贊和評(píng)論』,才是我創(chuàng)造的動(dòng)力。
- 關(guān)注小編,同時(shí)可以期待后續(xù)文章ing??,不定期分享原創(chuàng)知識(shí)。
- 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識(shí)點(diǎn),請(qǐng)移步前往小編:
https://gitee.com/MNxiaona/733GH/blob/master/jianshu
