跳轉到主要內容
本指南演示如何將在 開發環境 中獨立編譯的 C/C++ 庫集成到 arkffi 項目中,並通過 dlopen 調用其函數。

步驟概覽

  1. 在 開發環境 中創建 C/C++ 庫項目
  2. 使用 HarmonyOS NDK 交叉編譯爲 arm64-v8a 架構
  3. 將編譯產物放入 library/libs/arm64-v8a/
  4. 修改 CMakeLists.txt 引入預構建庫
  5. 在 ArkTS 中通過 dlopen 加載並調用

1. 創建外部庫項目

自行創建 C/C++ 庫項目。 源代碼 library.cpp
#ifdef __cplusplus
extern "C" {
#endif

    const char* hello(void) {
        return "Hello, World!";
    }

#ifdef __cplusplus
}
#endif
extern "C" 防止 C++ 編譯器對函數名進行改編(name mangling),確保 dlsym("hello") 能正確找到符號。如果不加,編譯後的符號名會被改成 _Z5hellov,導致 dlopen 無法識別。
CMakeLists.txt
cmake_minimum_required(VERSION 3.3)
project(rvohostest)
set(CMAKE_CXX_STANDARD 14)
add_library(rvohostest SHARED library.cpp)

2. 交叉編譯爲 HarmonyOS

在 開發環境 中使用 HarmonyOS NDK 的交叉編譯器進行編譯:
# 設置 NDK 工具鏈路徑(根據你的 SDK 實際路徑調整)
export TOOLCHAIN=/Applications/DevEco-Studio.app/contents/sdk/default/openharmony/native/llvm
export SYSROOT=/Applications/DevEco-Studio.app/contents/sdk/default/openharmony/native/sysroot

# 編譯 (arm64-v8a 架構)
$TOOLCHAIN/bin/aarch64-linux-ohos-clang++ \
  --sysroot=$SYSROOT \
  -fPIC \
  -shared \
  -o librvohostest.so \
  library.cpp
確保使用 HarmonyOS NDK 的 aarch64-linux-ohos-* 工具鏈,而非 macOS 自帶的 Clang。-fPIC 是編譯共享庫必需的選項。

3. 放置編譯產物

將編譯生成的 librvohostest.so 放入項目的以下目錄:
library/libs/arm64-v8a/librvohostest.so
如果項目還支持 x86_64 模擬器,需要在 library/libs/x86_64/ 下也放置對應架構的編譯產物。本示例僅編譯 arm64-v8a,因此 abiFilters 也僅包含此架構。

4. 修改 CMakeLists.txt

library/src/main/cpp/CMakeLists.txt 中,通過 IMPORTED 方式引入預構建的 .so
# 原有的 ffi_target 庫
add_library(ffi_target SHARED
    third_party/ffi_target.c
)

# ── 新增:引入外部預構建庫 ──
add_library(rvohostest SHARED IMPORTED)
set_target_properties(rvohostest
PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/librvohostest.so
)

# 將外部庫鏈接到主 NAPI 模塊
add_library(library SHARED napi_init.cpp)
target_link_libraries(library PUBLIC libace_napi.z.so rvohostest)

關鍵說明

配置項說明
SHARED IMPORTED聲明這是一個已預編譯好的共享庫,本項目不參與編譯
IMPORTED_LOCATION指向 .so 文件的路徑,${OHOS_ARCH} 在構建時自動替換爲 arm64-v8ax86_64
target_link_libraries將外部庫鏈接到 library,使得 librvohostest.so 隨 HAR 模塊一起打包到 HAP 中

5. 配置 abiFilters

確保 library/build-profile.json5abiFilters 包含編譯產物對應的架構:
{
  "buildOption": {
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "abiFilters": ["arm64-v8a"]
    }
  }
}

6. 在 ArkTS 中調用

確認 library/src/main/ets/ffi.ts 導出了需要的類:
export { dlopen, FFIType, CString, CFunction, JSCallback, Library } from './ffi';

7. 編寫測試用例

library/src/ohosTest/ets/test/FFI.test.ets 中使用 dlopen 加載外部庫並調用函數:
import { dlopen, FFIType, CString } from '../../../main/ets/ffi';

it('External Built Library', 0, () => {
  // 1. dlopen 加載外部共享庫。系統會按照以下順序查找 .so:
  //    - 應用原生庫路徑(/data/storage/el1/bundle/libs/arm64/)
  //    - 系統庫路徑
  //    由於我們在 CMakeLists.txt 中鏈接了 librvohostest.so,
  //    構建系統會自動將其打包到 HAP 中並部署到上述路徑。
  let lib1 = dlopen("librvohostest.so", {

    // 2. 定義函數簽名:hello() 返回 const char*(即 C 字符串指針)
    //    注意返回類型使用 FFIType.int64,因爲函數返回的是指針地址(64 位整數)。
    //    不能使用 FFIType.CString 作爲返回類型——底層類型分發器將其視爲字符串參數類型,而非返回類型。
    hello: { args: [], returns: FFIType.int64 },
  });

  // 3. 調用函數,獲取 C 字符串指針(一個 64 位內存地址)
  const ptr = lib1.symbols.hello();

  // 4. 通過 CString 將指針轉換爲 ArkTS 字符串
  //    CString 內部使用 readCString 讀取 null 結尾的 C 字符串
  const cstr = new CString(ptr);

  // 5. 驗證結果
  expect(cstr.toString()).assertEqual("Hello, World!");

  // 6. 關閉庫,釋放資源
  lib1.close();
});

注意事項

  • 返回 const char* 的函數必須使用 returns: FFIType.int64 而非 returns: FFIType.CString,因爲底層分發器將 's' 編碼視爲參數類型,不處理爲返回類型。
  • 拿到指針後需通過 CString 類讀取實際字符串內容。
  • 確保庫已用 extern "C" 編譯,否則 dlsym 因名字改編而找不到函數。
  • 關閉庫後不要再調用其符號,否則行爲未定義。

完整示例

請參見項目中的測試用例:
// file: library/src/ohosTest/ets/test/FFI.test.ets
it('External Built Library', 0, () => {
  let lib1 = dlopen("librvohostest.so", {
    hello: { args: [], returns: FFIType.int64 },
  });
  const ptr = lib1.symbols.hello();
  const cstr = new CString(ptr);
  expect(cstr.toString()).assertEqual("Hello, World!");
  lib1.close();
});

故障排查

問題原因解決
dlsym failed: Symbol not found: helloC++ name mangling在源碼中添加 extern "C"
Handle is invalid.so 未正確打包到 HAP檢查 CMakeLists.txtIMPORTED_LOCATION 路徑和 target_link_libraries
cannot locate symbol.so 依賴的運行時庫缺失確認使用 HarmonyOS NDK 編譯,而非宿主系統工具鏈
調用返回空字符串指針讀取錯誤確認返回類型使用 FFIType.int64 而非 FFIType.CString