天天看點

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

本文通過一個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規範開發的原生子產品擴充開發架構。
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

::: 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裡面不支援加注釋,後面編譯的時候會莫名其妙報錯。别問,問就是筆者踩過坑了。(好像也沒必要加注釋)
    #打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
    需要明白以下知識點:
"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開發[1]—Hello OpenHarmony NAPI

注冊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開發[1]—Hello OpenHarmony NAPI

注冊NAPI子產品總結

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony 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": []
        }
      ]
    }
           
  • "subsystem": "mysubsys",

    表示添加的子系統是mysubsys
  • "component": "hello",

    表示添加的子系統中包含的元件名稱是hello

編譯燒錄

關于這部分的内容可以參考筆者三方庫移植系列文章 https://ost.51cto.com/posts/16848#OpenHarmonySpeexdsp_25

先進行增量編譯出子系統的動态庫,增量編譯沒有報錯後。再全量編譯出鏡像,将其燒錄到開發闆上。

  • 增量編譯指令
./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64
           
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
  • 全量編譯和燒錄

    這部分的内容不重複叙述,大家可以參考社群文章 https://ost.51cto.com/posts/16203

    鏡像檔案在源碼目錄下位置如下:

    #打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

調用接口

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。

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

full-SDK替換請參考官方文檔: full-SDK替換指南

若提示找不到npm,需要配置一下環境變量,将以下路徑添加到環境變量中即可

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

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

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

建立OpenHarmony标準應用

建立項目,選擇OpenHarmony。

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

compile sdk選擇9,其他保持預設即可。

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
插一句題外話,3.1release版本釋出的時候。華為是把DevEco Studio分成了OpenHarmony和HarmonyOS兩個版本的,現在又合并到一起了。感興趣的讀者可以查閱筆者文章 https://ost.51cto.com/posts/11168
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

調用接口

第一步:調用方式和ArkUI架構提供的API一樣,先import引入擴充的NAPI子產品,後直接調用。

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony 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檔案中定義的動态庫名稱
    #打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

@ohos.hellonapi.d.ts内容如下:

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還是會報錯找不到該檔案。
  • declare namespace hellonapi

    export default hellonapi

    hellonapi

    是BUILD.gn中的定義的ohos_shared_library_name。
  • function getHelloString(): string;

    中的

    getHelloString()

    是hellonapi.cpp檔案中指定的子產品注冊對外接口的處理函數

則IDE掃描如下:

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

标準應用編譯不是強依賴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會報錯

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

第三步:選擇自動簽名

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI
#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

第四步:将應用安裝到dayu200開發闆上

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

運作效果如下:

#打卡不停更#三方庫移植之NAPI開發[1]—Hello OpenHarmony NAPI

知識點附送

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

繼續閱讀