鴻蒙HarmonyOS應(yīng)用開(kāi)發(fā)之使用Node-API實(shí)現(xiàn)跨語(yǔ)言交互開(kāi)發(fā)流程

使用Node-API實(shí)現(xiàn)跨語(yǔ)言交互,首先需要按照Node-API的機(jī)制實(shí)現(xiàn)模塊的注冊(cè)和加載等相關(guān)動(dòng)作。

  • ArkTS/JS側(cè):實(shí)現(xiàn)C++方法的調(diào)用。代碼比較簡(jiǎn)單,import一個(gè)對(duì)應(yīng)的so庫(kù)后,即可調(diào)用C++方法。

  • Native側(cè):.cpp文件,實(shí)現(xiàn)模塊的注冊(cè)。需要提供注冊(cè)lib庫(kù)的名稱,并在注冊(cè)回調(diào)方法中定義接口的映射關(guān)系,即Native方法及對(duì)應(yīng)的JS/ArkTS接口名稱等。

此處以在ArkTS/JS側(cè)實(shí)現(xiàn)add()接口、在Native側(cè)實(shí)現(xiàn)Add()接口,從而實(shí)現(xiàn)跨語(yǔ)言交互為例,呈現(xiàn)使用Node-API進(jìn)行跨語(yǔ)言交互的流程。

創(chuàng)建Native C++工程

  • 在DevEco Studio中New > Create Project,選擇Native C++模板,點(diǎn)擊Next,選擇API版本,設(shè)置好工程名稱,點(diǎn)擊Finish,創(chuàng)建得到新工程。
  • 創(chuàng)建工程后工程結(jié)構(gòu)可以分兩部分,cpp部分和ets部分,具體可見(jiàn)下文的工程目錄介紹。

主要工程目錄介紹

  • entry > src > main > cpp > types:用于存放C++的API接口描述文件。
  • entry > src > main > cpp > types > libentry > index.d.ts:描述C++ API接口行為,如接口名、入?yún)ⅰ⒎祷貐?shù)等。
  • entry > src > main > cpp > types > libentry > oh-package.json5:配置.so三方包聲明文件的入口及包名。
  • entry > src > main > cpp > CMakeLists.txt:C++源碼編譯配置文件,提供CMake構(gòu)建腳本。
  • entry > src > main > cpp > hello.cpp:定義C++ API接口的文件。
  • entry > src > main > ets:用于存放ArkTS源碼。

Native側(cè)方法的實(shí)現(xiàn)

  • 設(shè)置模塊注冊(cè)信息

    ArkTS側(cè)import native模塊時(shí),會(huì)加載其對(duì)應(yīng)的so。加載so時(shí),首先會(huì)調(diào)用napi_module_register方法,將模塊注冊(cè)到系統(tǒng)中,并調(diào)用模塊初始化函數(shù)。

    napi_module有兩個(gè)關(guān)鍵屬性:一個(gè)是.nm_register_func,定義模塊初始化函數(shù);另一個(gè)是.nm_modname,定義模塊的名稱,也就是ArkTS側(cè)引入的so庫(kù)的名稱,模塊系統(tǒng)會(huì)根據(jù)此名稱來(lái)區(qū)分不同的so。

// entry/src/main/cpp/hello.cpp

// 準(zhǔn)備模塊加載相關(guān)信息,將上述Init函數(shù)與本模塊名等信息記錄下來(lái)。
static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = nullptr,
    .reserved = {0},
};

// 加載so時(shí),該函數(shù)會(huì)自動(dòng)被調(diào)用,將上述demoModule模塊注冊(cè)到系統(tǒng)中。
extern "C" __attribute__((constructor)) void RegisterDemoModule() { 
    napi_module_register(&demoModule);
 }
  • 模塊初始化

    實(shí)現(xiàn)ArkTS接口與C++接口的綁定和映射。

// entry/src/main/cpp/hello.cpp
EXTERN_C_START
// 模塊初始化
static napi_value Init(napi_env env, napi_value exports) {
    // ArkTS接口與C++接口的綁定和映射
    napi_property_descriptor desc[] = {
        {"callNative", nullptr, CallNative, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"nativeCallArkTS", nullptr, NativeCallArkTS, nullptr, nullptr, nullptr, napi_default, nullptr},
    };
    // 在exports對(duì)象上掛載CallNative/NativeCallArkTS兩個(gè)Native方法
    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 = nullptr,
    .reserved = {0},
};
  • 在index.d.ts文件中,提供JS側(cè)的接口方法。
// entry/src/main/cpp/types/libentry/index.d.ts
export const callNative: (a: number, b: number) => number;
export const nativeCallArkTS: (cb: (a: number) => number) => number;
  • 在oh-package.json5文件中將index.d.ts與cpp文件關(guān)聯(lián)起來(lái)。
{
  "name": "libentry.so",
  "types": "./index.d.ts",
  "version": "",
  "description": "Please describe the basic information."
}
  • 在CMakeLists.txt文件中配置CMake打包參數(shù)。
# entry/src/main/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(MyApplication2)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

# 添加名為entry的庫(kù)
add_library(entry SHARED hello.cpp)
# 構(gòu)建此可執(zhí)行文件需要鏈接的庫(kù)
target_link_libraries(entry PUBLIC libace_napi.z.so)
  • 實(shí)現(xiàn)Native側(cè)的CallNative以及NativeCallArkTS接口。具體代碼如下:
// entry/src/main/cpp/hello.cpp
static napi_value CallNative(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    // 聲明參數(shù)數(shù)組
    napi_value args[2] = {nullptr};

    // 獲取傳入的參數(shù)并依次放入?yún)?shù)數(shù)組中
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 依次獲取參數(shù)
    double value0;
    napi_get_value_double(env, args[0], &value0);
    double value1;
    napi_get_value_double(env, args[1], &value1);

    // 返回兩數(shù)相加的結(jié)果
    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);
    return sum;
}

static napi_value NativeCallArkTS(napi_env env, napi_callback_info info)
{    
    size_t argc = 1;
    // 聲明參數(shù)數(shù)組
    napi_value args[1] = {nullptr};

    // 獲取傳入的參數(shù)并依次放入?yún)?shù)數(shù)組中
    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    // 創(chuàng)建一個(gè)int,作為ArkTS的入?yún)?    napi_value argv = nullptr;    
    napi_create_int32(env, 2, &argv );

    // 調(diào)用傳入的callback,并將其結(jié)果返回
    napi_value result = nullptr;
    napi_call_function(env, nullptr, args[0], 1, &argv, &result);
    return result;
}

ArkTS側(cè)調(diào)用C/C++方法實(shí)現(xiàn)

ArkTS側(cè)通過(guò)import引入Native側(cè)包含處理邏輯的so來(lái)使用C/C++的方法。

// entry/src/main/ets/pages/Index.ets
// 通過(guò)import的方式,引入Native能力。
import nativeModule from 'libentry.so'

@Entry
@Component
struct Index {
  @State message: string = 'Test Node-API callNative result: ';
  @State message2: string = 'Test Node-API nativeCallArkTS result: ';
  build() {
    Row() {
      Column() {
        // 第一個(gè)按鈕,調(diào)用add方法,對(duì)應(yīng)到Native側(cè)的CallNative方法,進(jìn)行兩數(shù)相加。
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.message += nativeModule.callNative(2, 3);
            })
        // 第二個(gè)按鈕,調(diào)用nativeCallArkTS方法,對(duì)應(yīng)到Native的NativeCallArkTS,在Native調(diào)用ArkTS function。
        Text(this.message2)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.message2 += nativeModule.nativeCallArkTS((a: number)=> {
                return a * 2;
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Node-API的約束限制

SO命名規(guī)則

導(dǎo)入使用的模塊名和注冊(cè)時(shí)的模塊名大小寫保持一致,如模塊名為entry,則so的名字為libentry.so,napi_module中nm_modname字段應(yīng)為entry,ArkTS側(cè)使用時(shí)寫作:import xxx from 'libentry.so'。

注冊(cè)建議

  • nm_register_func對(duì)應(yīng)的函數(shù)(如上述Init函數(shù))需要加上static,防止與其他so里的符號(hào)沖突;
  • 模塊注冊(cè)的入口,即使用attribute((constructor))修飾的函數(shù)的函數(shù)名(如上述RegisterDemoModule函數(shù))需要確保不與其它模塊重復(fù)。

多線程限制

每個(gè)引擎實(shí)例對(duì)應(yīng)一個(gè)JS線程,實(shí)例上的對(duì)象不能跨線程操作,否則會(huì)引起應(yīng)用crash。使用時(shí)需要遵循如下原則:

  • Node-API接口只能在JS線程使用。
  • Native接口入?yún)nv與特定JS線程綁定只能在創(chuàng)建時(shí)的線程使用。

寫在最后

  • 如果你覺(jué)得這篇內(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容