本文通過一個Hello OpenHarmony NAPI樣例講述了NPAI接口開發基礎知識。開發基于最新的OpenHarmony3.2Beta3版本及其對應SDK。标準系統開發闆為潤和軟體dayu200。
将C/C++ 三方庫移植到OpenHarmony标準系統後,需要通過NAPI架構将其C/C++ 接口轉換成JS/ETS接口給應用層調用。
- OpenHarmony标準系統三方庫移植可參考《啃論文俱樂部——移植speexdsp到OpenHarmony标準系統》系列文章
(目錄)
通過本文您将熟悉:
- 如何注冊NAPI子產品及接口。
- 如何在ArkUI eTS代碼中調用擴充的NAPI接口。
- full-SDK的替換。
什麼是NAPI
- NAPI(Native API)元件是一套對外接口基于Node.js N-API規範開發的原生子產品擴充開發架構。
::: hljs-center
NAPI元件架構圖
:::
- OpenHarmony 标準系統應用開發基于ArkUI架構,開發語言使用JS/eTS。部分業務場景依賴使用現有的C/C++ 庫,或為了擷取更高的性能。OpenHarmony提供NAPI機制,用于規範封裝IO、CPU密集型、OS底層等能力并對外暴露JS接口,通過NAPI實作JS和C/C++代碼的互相通路.
- 例如: 鐘祿平和林嘉誠老師在如何在OpenHarmony上使用SeetaFace2人臉識别庫?一文中,重點講解了NAPI接口如何實作OpenCV以及SeetaFace的調用。一句話概括就是,鐘祿平和林嘉誠老師講述了移植了三方庫後通過NAPI将庫的C/C++接口變成JS/ETS接口給應用層調用。
- OpenHarmony 中的 N-API 定義了由 JS/ETS 語言編寫的代碼和 native 代碼(使用 C/C++ 編寫)互動的方式,由 Node.js N-API 架構擴充而來。
- N-API:Native Application Programming Interface(本地應用程式接接口)
什麼是Node.js N-API 架構
Node.js N-API為開發者提供了一套C/C++ API用于開發Node.js的Native擴充子產品。從Node.js 8.0.0開始,N-API以實驗性特性作為Node.js本身的一部分被引入,并且從Node.js 10.0.0開始正式全面支援N-API。
添加OpenHarmony自定義子系統、元件、子產品
- 這部分内容涉及三方庫移植,為便于本篇NAPI基礎的學習。筆者在此自定義一個子系統用于開發NAPI。如在已存在的子系統元件中添加擴充NAPI,則跳過此步。
- 需要準備好OpenHarmonyBeta3源碼和編譯環境
- 筆者的編譯環境為WSL2+Ubuntu18.04+vscode,搭建筆者一樣的編譯環境搭建可以參考 https://ost.51cto.com/posts/17164
添加子系統、元件
直接在OpenHarmony源碼根目錄建立子系統檔案夾,取名mysubsys。并在目錄下添加子系統的建構配置檔案ohos.build
完整内容如下:
{
"subsystem": "mysubsys",
"parts": {
"hello": {
"module_list": [
"//mysubsys/hello/hellonapi:hellonapi"
],
"inner_kits": [
],
"system_kits": [
],
"test_list": [
]
}
}
}
- 另外ohos.build裡面不支援加注釋,後面編譯的時候會莫名其妙報錯。别問,問就是筆者踩過坑了。(好像也沒必要加注釋) 需要明白以下知識點:
"subsystem": "mysubsys",
- subsystem後面的mysubsy是子系統的名稱。
"parts": {
"hello": {
}
}
- hello是元件名稱,被mysubsys子系統包含
"module_list": [
"//mysubsys/hello/hellonapi:hellonapi"
- hellonapi是子產品名,被hello元件包含。
接着将子系統配置到源碼下build\subsystem_config.json檔案,在該檔案中插入如下内容。
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys",
"dir": ""
}
- OpenHarmony系統架構中,子系統是一個邏輯概念,它具體由對應的元件構成。元件是對子系統的進一步拆分,可複用的軟體單元,它包含源碼、配置檔案、資源檔案和編譯腳本;能獨立建構,以二進制方式內建,具備獨立驗證能力的二進制單元。
本示例按子系統system > 元件part > 元件module 建立了3級目錄
mysubsys -- 子系統目錄
├── hello -- 元件目錄
│ └── hellonapi
│ ├── BUILD.gn -- 元件module目錄
│ └── hellonapi.cpp
└── ohos.build
源碼實作
最後在元件目錄下中建立代碼檔案hellonapi.cpp
完整内容如下:
#include <string.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
// 接口業務實作C/C++代碼
// std::string 需要引入string頭檔案,#include <string>
// 該napi_module對外具體的提供的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_addon_register_func
// 2.指定NAPI子產品注冊對外接口的處理函數,具體擴充的接口在該函數中聲明
// 子產品對外接口注冊函數為registerFunc
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
// 聲明該napi_module對外具體的提供的API為getHelloString
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
// 注冊NAPI子產品
// 1.先定義NAPI子產品,指定目前NAPI子產品對應的子產品名
// 以及子產品注冊對外接口的處理函數,具體擴充的接口在該函數中聲明
// nm_modname: NAPI子產品名稱,對應eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
// 示例對應hap應用中eTS代碼需要包含import hellonapi from '@ohos.hellonapi'
// 以下的出現的hellonapi都為注冊的NAPI子產品名
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
// registerFunc是NAPI子產品對外接口注冊函數
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
// 3.NAPI子產品定義好後,調用NAPI提供的子產品注冊函數napi_module_register(napi_module* mod)函數注冊到系統中。
// register module,裝置啟動時自動調用此constructor函數,把定義的子產品注冊到OpenHarmony中。
// 以下出現的hellonapi都是注冊的NAPI子產品名
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
// napi_module_register是ohos的NAPI元件提供的子產品注冊函數
napi_module_register(&hellonapiModule);
}
代碼解析如下:
接口業務實作C/C++代碼
// 接口業務實作C/C++代碼
// std::string 需要引入string頭檔案,#include <string>
// 該napi_module對外具體的提供的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提供了提供了一系列接口函數,聲明包含如下2個頭檔案中,先添加這2個頭檔案到hellonapi.cpp
#include "napi/native_api.h"
#include "napi/native_node_api.h"
- native_api.h和native_node_api.h這兩個頭檔案
- 在OpenHarmony3.1release源碼中在//foundation/ace/napi/interfaces/kits目錄下
- 在OpenHarmony3.2beta3源碼中分别在//foundation/arkui/napi/interfaces/kits和//foundation/arkui/napi/interfaces/inner_api目錄下了。
注冊NAPI子產品、添加接口聲明
定義的hellonapi子產品,其對應結構體為napi_module。
- 指定目前NAPI子產品對應的子產品名
- 子產品注冊對外接口的處理函數,具體擴充的接口在該函數中聲明。
// 注冊對外接口的處理函數napi_addon_register_func
// 2.指定NAPI子產品注冊對外接口的處理函數,具體擴充的接口在該函數中聲明
// 子產品對外接口注冊函數為registerFunc
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
// 聲明該napi_module對外具體的提供的API為getHelloString
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
// 注冊NAPI子產品
// 1.先定義NAPI子產品,指定目前NAPI子產品對應的子產品名
// 以及子產品注冊對外接口的處理函數,具體擴充的接口在該函數中聲明
// nm_modname: NAPI子產品名稱,對應eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
// 示例對應hap應用中eTS代碼需要包含import hellonapi from '@ohos.hellonapi'
// 以下的出現的hellonapi都為注冊的NAPI子產品名
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
// registerFunc是該自定義的NAPI子產品對外接口注冊函數
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
// 3.NAPI子產品定義好後,調用ohos的NAPI元件提供的子產品注冊函數napi_module_register(napi_module* mod)函數注冊到系統中。
// register module,裝置啟動時自動調用constructor函數,把定義的子產品注冊到OpenHarmony中。
// 以下出現的hellonapi都是注冊的NAPI子產品名
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
// napi_module_register(napi_module* mod)是ohos的NAPI元件提供的子產品注冊函數
napi_module_register(&hellonapiModule);
}
- napi_module_register(napi_module* mod)是ohos的NAPI元件提供的子產品注冊函數。
- 該函數在源碼目錄下foundation/arkui/napi/native_engine/native_node.cpp
注冊NAPI子產品總結
自定義子系統建構
hellonapi編譯gn化,新增gn工程建構腳本。
在子產品hellonapi目錄下建立BUILD.gn檔案,内容如下:
gn檔案支援注釋,以 #
開頭
import("//build/ohos.gni")
#ohos_shared_library()中的hellonapi決定了生成動态庫的名稱,增量編譯階段生成動态庫libhellonapi.z.so
ohos_shared_library("hellonapi") {
include_dirs = [
#NAPI頭檔案目錄
"//foundation/arkui/napi/interfaces/kits",
"//foundation/arkui/napi/interfaces/inner_api",
#根據增量編譯階段報錯添加的頭檔案目錄
"//third_party/node/src"
]
#根據增量編譯時clang編譯器報警,添加的cflag
cflags_cc = [
#編譯時報錯提示"-Werror",則加上"-Wno-error"
"-Wno-error",
#編譯時報錯提示"-Wunused-function",則加上"-Wno-unused-function"
"-Wno-unused-function",
]
#編譯需要的源檔案
sources = [
"hellonapi.cpp"
]
#指定編譯依賴libace_napi.z.so動态庫
deps = [ "//foundation/arkui/napi:ace_napi" ]
#指定庫生成的路徑
#libhellonapi.z.so會安裝在rk3568開發闆的system/lib/module目錄下
relative_install_dir = "module"
#子系統名稱是mysubsys
subsystem_name = "mysubsys"
#元件名稱是hello
part_name = "hello"
}
修改産品配置
将元件添加到需要的産品配置檔案,源碼目錄下的productdefine/common/products/ohos-arm64.json。
- 插入位置任意,但要注意行尾的逗号,確定格式json檔案格式正确。
"parts":{
...
"mysubsys:hello":{},
...
}
- mysubsys是本示例自定義的子系統名稱
- hello是自定義子系統下的元件名稱
- parts格式如下:
"parts":{
"部件所屬子系統名:部件名":{}
}
修改build/subsystem_config.json
新增子系統定義。
- subsystem_config.json檔案定義了有哪些子系統以及這些子系統所在檔案夾路徑,添加子系統時需要說明子系統path與name,分别表示子系統路徑和子系統名。
注意json檔案也不支援注釋!!!
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys"
}
-
表示子系統路徑"path": "mysubsys",
-
表示子系統名稱"name": "mysubsys"
修改vendor/hihope/rk3568/config.json檔案
将mysubsys子系統添加至rk3568開發闆,在vendor目錄下新增産品的定義。
{
"subsystem": "mysubsys",
"components": [
{
"component": "hello",
"features": []
}
]
}
-
表示添加的子系統是mysubsys"subsystem": "mysubsys",
-
表示添加的子系統中包含的元件名稱是hello"component": "hello",
編譯燒錄
關于這部分的内容可以參考筆者三方庫移植系列文章 https://ost.51cto.com/posts/16848#OpenHarmonySpeexdsp_25
先進行增量編譯出子系統的動态庫,增量編譯沒有報錯後。再全量編譯出鏡像,将其燒錄到開發闆上。
- 增量編譯指令
./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64
-
全量編譯和燒錄
這部分的内容不重複叙述,大家可以參考社群文章 https://ost.51cto.com/posts/16203
鏡像檔案在源碼目錄下位置如下:
調用接口
full-SDK替換(可選)
從OpenHarmony 3.2 Beta2起,SDK會同時提供Public SDK和Full SDK。通過DevEco Studio預設擷取的SDK為Public SDK。
兩者差異如下:
- Public SDK
- 面向應用開發者提供,不包含需要使用系統權限的系統接口。通過DevEco Studio預設擷取的SDK為Public SDK。
- Full SDK
- 面向OEM廠商提供,包含了需要使用系統權限的系統接口。使用Full SDK時需要手動從鏡像站點擷取,并在DevEco Studio中替換
筆者使用的DevEco Studio版本為3.0.0.993,即DevEco Studio 3.0。API為API9。
full-SDK替換請參考官方文檔: full-SDK替換指南
若提示找不到npm,需要配置一下環境變量,将以下路徑添加到環境變量中即可
D:\DevEco Studio\ohos\sdk\ets\build-tools\ets-loader
建立OpenHarmony标準應用
建立項目,選擇OpenHarmony。
compile sdk選擇9,其他保持預設即可。
插一句題外話,3.1release版本釋出的時候。華為是把DevEco Studio分成了OpenHarmony和HarmonyOS兩個版本的,現在又合并到一起了。感興趣的讀者可以查閱筆者文章 https://ost.51cto.com/posts/11168
調用接口
第一步:調用方式和ArkUI架構提供的API一樣,先import引入擴充的NAPI子產品,後直接調用。
index.ets内容如下:
import prompt from '@system.prompt'
// 引入擴充的NAPI子產品
// 在hellonapi.cpp檔案中定義nm_modname(子產品名稱)為hellonapi
// 在BUILD.gn檔案中定義ohos_shared_library結構體名稱為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對外具體的提供的API是getHelloString
let strFromNAPI = hellonapi.getHelloString()
prompt.showToast({ message: strFromNAPI })
})
}
.width('100%')
.height('100%')
}
}
第二步(可選):參考其他子產品的.d.ts建立擴充子產品@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檔案中定義的動态庫名稱
@ohos.hellonapi.d.ts内容如下:
declare namespace hellonapi {
function getHelloString(): string;
/**
*
*
* @since 9
* @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
*/
}
export default hellonapi;
-
表示API的版本為9@since 9
-
語句在.d.ts檔案中一定要添加,否則IDE還是會報錯找不到該檔案。@syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
-
和declare namespace hellonapi
的export default hellonapi
是BUILD.gn中的定義的ohos_shared_library_name。hellonapi
-
中的function getHelloString(): string;
是hellonapi.cpp檔案中指定的子產品注冊對外接口的處理函數getHelloString()
則IDE掃描如下:
标準應用編譯不是強依賴OpenHarmony SDK,是以可忽略IDE中告警,直接編譯打包hap。但是有的時候IDE會提示找不到@ohos.hellonapi.d.ts,然後有小機率的機會無法安裝hap。這個時候就要參考ohos\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會報錯
第三步:選擇自動簽名
第四步:将應用安裝到dayu200開發闆上
運作效果如下:
知識點附送
Native API中支援的标準庫
表1 OpenHarmony支援的标準庫
名稱 | 簡介 |
---|---|
标準C庫 | libc、libm、libdl組合實作C11标準C庫。 |
标準C++庫 | libc++ 是C++标準庫的一種實作。 |
OpenSL ES | OpenSL ES是一個嵌入式跨平台的音頻處理庫。 |
zlib | Zlib是基于C/C++語言實作的一個通用的資料壓縮庫。 |
EGL | EGL是渲染API與底層原生視窗系統之間的一種标準的軟體接口。 |
OpenGL ES | OpenGL ES是一個嵌入式跨平台的為 3D 圖形處理硬體指定标準的軟體接口。 |
學習資料
OpenHarmony 源碼解析之NAPI架構内部實作分析
編譯時子產品配置規則
編譯時部件配置規則
編譯時子系統配置規則
編譯時産品配置規則
附件連結:https://ost.51cto.com/resource/2319