C/C++三方库移植到HarmonyOS平台详细教程(补充版so库和头文件形式)

接上篇文章,上篇介绍了在有源码的情况下的编译。更多时候三方库的移植,可能只提供给你了arm64平台下的so库和头文件。那么如何移植到harmonyOS平台下呢?总结一句话就是利用so库和头文件,再封装成arkts侧能用的arkts接口,使用napi或者aki的方式。

📋 目录

  1. 移植概述
  2. 移植方式选择
  3. 源码方式移植
  4. 预编译so库方式移植
  5. 构建配置
  6. 实际案例
  7. 最佳实践
  8. 常见问题

预编译so库方式移植

在实际项目中,我们经常需要将C/C++库先编译为so库,然后提供头文件的方式给其他项目使用。这种方式有以下优势:

  • 模块化: 将不同功能模块化,提升代码复用性
  • 团队协作: 不同团队可以独立开发和维护
  • 版本管理: 可以独立管理库的版本
  • 编译效率: 避免重复编译,提高构建效率

预编译so库的两种集成方式

方式一:Native侧引用三方so库

这种方式适用于需要在Native侧调用so库功能的场景。

方案A:通过编译动态链接库的方式引用

实现原理: 将so库加入到工程中,在Native侧使用CMake编译动态链接库,通过include引用头文件调用功能函数。

开发步骤:

  1. 准备so库和头文件
# 项目结构
your_project/
├── entry/
│   ├── libs/
│   │   ├── arm64/
│   │   │   └── libyour_library.so
│   │   └── x86_64/
│   │       └── libyour_library.so
│   └── src/
│       └── main/
│           ├── cpp/
│           │   ├── your_library.h          # 头文件
│           │   ├── napi_init.cpp           # Node-API包装器
│           │   └── CMakeLists.txt
│           └── ets/
│               └── pages/
│                   └── Index.ets
  1. 配置CMakeLists.txt
# entry/src/main/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(YourLibrary)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

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

# 添加名为entry的库
add_library(entry SHARED napi_init.cpp)

# 链接预编译的so库
target_link_libraries(entry PUBLIC 
    ${NATIVERENDER_ROOT_PATH}/../../../libs/${OHOS_ARCH}/libyour_library.so
    libace_napi.z.so
)
  1. 实现Node-API包装器
    为什么要再包装一层?因为c的so库,最终我们是想在arkts层使用的。因此需要包装一下,按照napi的接口规范,实现c接口到arkts接口的转换。

在这里插入图片描述

// entry/src/main/cpp/napi_init.cpp
#include <napi.h>
#include "your_library.h"  // 包含预编译库的头文件
#include <string>

// 包装预编译库的函数
static napi_value YourFunction(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    // 获取参数
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 参数验证
    if (argc < 1) {
        napi_throw_error(env, nullptr, "需要至少1个参数");
        return nullptr;
    }

    // 获取字符串参数
    char input[256];
    size_t input_len;
    napi_get_value_string_utf8(env, args[0], input, sizeof(input), &input_len);

    // 调用预编译库的函数
    std::string result = your_library_function(input);

    // 返回结果
    napi_value result_value;
    napi_create_string_utf8(env, result.c_str(), NAPI_AUTO_LENGTH, &result_value);
    return result_value;
}

// 模块初始化
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"yourFunction", nullptr, YourFunction, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}

// 模块注册
static napi_module yourModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterYourModule() { 
    napi_module_register(&yourModule);
}
  1. 创建TypeScript类型定义
// entry/src/main/cpp/types/libentry/index.d.ts
export const yourFunction: (input: string) => string;
  1. 在ArkTS中使用
// entry/src/main/ets/pages/Index.ets
import nativeModule from 'libentry.so'

@Entry
@Component
struct Index {
  @State result: string = '';

  build() {
    Row() {
      Column() {
        Text('调用预编译库')
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.result = nativeModule.yourFunction('Hello from ArkTS!');
          })
        
        Text(this.result)
          .fontSize(30)
      }
      .width('100%')
    }
    .height('100%')
  }
}
方案B:通过调用dlopen的方式引用

实现原理: 将so库加入到工程中,在ArkTS侧将so库的沙箱路径传递至Native侧,在Native侧使用dlopen解析so库调用功能函数。

注意事项: 该方案只能引用C语言编译模式生成的so库,因此用于生成so库的.h头文件需要用extern “C” {}包裹。

开发步骤:

  1. 准备C语言接口的so库
// your_library.h (C语言接口)
#ifndef YOUR_LIBRARY_H
#define YOUR_LIBRARY_H

#ifdef __cplusplus
extern "C" {
#endif

// 导出函数声明
double add(double a, double b);
double sub(double a, double b);
const char* process_string(const char* input);

#ifdef __cplusplus
}
#endif

#endif // YOUR_LIBRARY_H
  1. 在ArkTS侧传递so库路径
// entry/src/main/ets/pages/Index.ets
import nativeModule from 'libentry.so'

@Entry
@Component
struct Index {
  @State result: string = '';

  build() {
    Row() {
      Column() {
        Text('调用dlopen方式')
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // 获取so库的沙箱路径
            let projectPath = this.getUIContext().getHostContext()!.bundleCodeDir;
            let abiPath = deviceInfo.abiList === 'x86_64' ? 'x86_64' : 'arm64';
            let soLibPath = `${projectPath}/libs/${abiPath}/libyour_library.so`;
            
            // 调用Native函数
            this.result = nativeModule.processWithDlopen('Hello', soLibPath);
          })
        
        Text(this.result)
          .fontSize(30)
      }
      .width('100%')
    }
    .height('100%')
  }
}
  1. 实现dlopen方式的Node-API包装器
// entry/src/main/cpp/napi_init.cpp
#include <napi.h>
#include <dlfcn.h>
#include <cstring>

// 定义函数指针类型
typedef const char* (*ProcessStringFunc)(const char*);

static napi_value ProcessWithDlopen(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    // 获取参数
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 获取输入字符串
    char input[256];
    size_t input_len;
    napi_get_value_string_utf8(env, args[0], input, sizeof(input), &input_len);

    // 获取so库路径
    size_t path_len = 0;
    napi_get_value_string_utf8(env, args[1], nullptr, 0, &path_len);
    char* path = new char[path_len + 1];
    napi_get_value_string_utf8(env, args[1], path, path_len + 1, &path_len);

    // 使用dlopen加载so库
    void* handle = dlopen(path, RTLD_LAZY);
    if (!handle) {
        napi_throw_error(env, nullptr, "Failed to load library");
        delete[] path;
        return nullptr;
    }

    // 获取函数符号
    ProcessStringFunc process_func = (ProcessStringFunc)dlsym(handle, "process_string");
    if (!process_func) {
        napi_throw_error(env, nullptr, "Failed to get function symbol");
        dlclose(handle);
        delete[] path;
        return nullptr;
    }

    // 调用函数
    const char* result = process_func(input);

    // 返回结果
    napi_value result_value;
    napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &result_value);

    // 清理资源
    dlclose(handle);
    delete[] path;

    return result_value;
}

// 模块初始化
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"processWithDlopen", nullptr, ProcessWithDlopen, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}

// 模块注册
static napi_module yourModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterYourModule() { 
    napi_module_register(&yourModule);
}
方式二:ArkTS侧直接引用三方so库

这种方式适用于so库已经适配了Native接口的场景。

实现原理: 将so库和对应的Native侧接口文件加入到工程中,在工程中配置so库对应的模块动态依赖,在ArkTS侧通过import引入依赖接口调用so库。

注意事项: 该方案只能引用适配Native的so库,因此在编译生成so库时需要实现功能函数并向Napi注册其Native侧接口,提供对应的Native侧接口文件index.d.ts和配置文件oh-package.json5。

开发步骤:

  1. 准备适配Native的so库
# 项目结构
your_project/
├── entry/
│   ├── libs/
│   │   ├── arm64/
│   │   │   └── libyour_library.so
│   │   └── x86_64/
│   │       └── libyour_library.so
│   └── src/
│       └── main/
│           ├── cpp/
│           │   └── types/
│           │       └── libyour_library/
│           │           ├── index.d.ts
│           │           └── oh-package.json5
│           └── ets/
│               └── pages/
│                   └── Index.ets
  1. 创建TypeScript类型定义
// entry/src/main/cpp/types/libyour_library/index.d.ts
export const yourFunction: (input: string) => string;
export const yourBufferFunction: (input: ArrayBuffer) => ArrayBuffer;
  1. 配置oh-package.json5
// entry/src/main/cpp/types/libyour_library/oh-package.json5
{
  "name": "libyour_library.so",
  "types": "./index.d.ts",
  "version": "1.0.0",
  "description": "Your library for HarmonyOS"
}
  1. 在模块级oh-package.json5中声明依赖
// entry/oh-package.json5
{
  "name": "entry",
  "version": "1.0.0",
  "dependencies": {
    "libyour_library.so": "file:./src/main/cpp/types/libyour_library"
  }
}
  1. 在ArkTS中直接使用
// entry/src/main/ets/pages/Index.ets
import yourLibrary from 'libyour_library.so'

@Entry
@Component
struct Index {
  @State result: string = '';

  build() {
    Row() {
      Column() {
        Text('直接调用so库')
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // 直接调用so库中的函数
            this.result = yourLibrary.yourFunction('Hello from ArkTS!');
          })
        
        Text(this.result)
          .fontSize(30)
      }
      .width('100%')
    }
    .height('100%')
  }
}

AKI方式集成预编译so库

方案A:链接预编译so库
// example_aki.cpp
#include <aki/jsbind.h>
#include <aki/array_buffer.h>
#include "your_library.h"  // 预编译库的头文件
#include <string>
#include <vector>

// 包装预编译库的函数
std::string YourFunction(const std::string& input) {
    // 直接调用预编译库的函数
    return your_library_function(input.c_str());
}

// 支持ArrayBuffer的函数
aki::ArrayBuffer YourBufferFunction(const aki::ArrayBuffer& input) {
    // 调用预编译库的二进制处理函数
    std::vector<uint8_t> result = your_library_process_buffer(
        input.GetData(), input.GetLength()
    );
    return aki::ArrayBuffer(result.data(), result.size());
}

// 注册AKI插件
JSBIND_ADDON(your_library_aki)

// 注册全局函数
JSBIND_GLOBAL() {
    JSBIND_FUNCTION(YourFunction);
    JSBIND_FUNCTION(YourBufferFunction);
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(your_library_aki)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置AKI根路径
set(AKI_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules/@ohos/aki)
set(CMAKE_MODULE_PATH ${AKI_ROOT_PATH})
find_package(Aki REQUIRED)

# 源文件
set(SOURCES
    example_aki.cpp         # AKI包装器
)

# 创建共享库
add_library(your_library_aki SHARED ${SOURCES})

# 链接AKI库和预编译的so库
target_link_libraries(your_library_aki PUBLIC 
    Aki::libjsbind
    ${CMAKE_CURRENT_SOURCE_DIR}/../libs/${OHOS_ARCH}/libyour_library.so
)

# 包含头文件路径
target_include_directories(your_library_aki PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../include
)

# 设置输出目录
set_target_properties(your_library_aki PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib
)
方案B:动态加载预编译so库
// example_aki_dynamic.cpp
#include <aki/jsbind.h>
#include <aki/array_buffer.h>
#include <dlfcn.h>
#include <string>
#include <vector>

// 定义函数指针类型
typedef const char* (*YourLibraryFunc)(const char*);
typedef void* (*YourBufferFunc)(const void*, size_t, size_t*);

// 全局句柄
void* library_handle = nullptr;
YourLibraryFunc your_library_function = nullptr;
YourBufferFunc your_buffer_function = nullptr;

// 初始化库
bool InitializeLibrary(const std::string& library_path) {
    if (library_handle) {
        return true; // 已经初始化
    }

    library_handle = dlopen(library_path.c_str(), RTLD_LAZY);
    if (!library_handle) {
        return false;
    }

    // 获取函数符号
    your_library_function = (YourLibraryFunc)dlsym(library_handle, "your_library_function");
    your_buffer_function = (YourBufferFunc)dlsym(library_handle, "your_buffer_function");

    if (!your_library_function || !your_buffer_function) {
        dlclose(library_handle);
        library_handle = nullptr;
        return false;
    }

    return true;
}

// 包装函数
std::string YourFunction(const std::string& input) {
    if (!your_library_function) {
        return "Library not initialized";
    }
    return your_library_function(input.c_str());
}

aki::ArrayBuffer YourBufferFunction(const aki::ArrayBuffer& input) {
    if (!your_buffer_function) {
        return aki::ArrayBuffer(nullptr, 0);
    }

    size_t output_size = 0;
    void* result = your_buffer_function(input.GetData(), input.GetLength(), &output_size);
    
    if (result && output_size > 0) {
        aki::ArrayBuffer buffer(result, output_size);
        // 注意:这里需要根据实际情况管理内存
        return buffer;
    }
    
    return aki::ArrayBuffer(nullptr, 0);
}

// 初始化函数
bool InitLibrary(const std::string& library_path) {
    return InitializeLibrary(library_path);
}

// 清理函数
void CleanupLibrary() {
    if (library_handle) {
        dlclose(library_handle);
        library_handle = nullptr;
        your_library_function = nullptr;
        your_buffer_function = nullptr;
    }
}

// 注册AKI插件
JSBIND_ADDON(your_library_aki_dynamic)

// 注册全局函数
JSBIND_GLOBAL() {
    JSBIND_FUNCTION(YourFunction);
    JSBIND_FUNCTION(YourBufferFunction);
    JSBIND_FUNCTION(InitLibrary);
    JSBIND_FUNCTION(CleanupLibrary);
}
// types/libyour_library_aki_dynamic/index.d.ts
export function YourFunction(input: string): string;
export function YourBufferFunction(input: ArrayBuffer): ArrayBuffer;
export function InitLibrary(library_path: string): boolean;
export function CleanupLibrary(): void;
// 在ArkTS中使用
import aki from 'libyour_library_aki_dynamic.so'

@Entry
@Component
struct DynamicLibraryExample {
  @State result: string = '';

  build() {
    Column() {
      Button('初始化库')
        .onClick(() => {
          let projectPath = this.getUIContext().getHostContext()!.bundleCodeDir;
          let abiPath = deviceInfo.abiList === 'x86_64' ? 'x86_64' : 'arm64';
          let soLibPath = `${projectPath}/libs/${abiPath}/libyour_library.so`;
          
          const success = aki.InitLibrary(soLibPath);
          console.log('Library initialized:', success);
        })

      Button('调用函数')
        .onClick(() => {
          this.result = aki.YourFunction('Hello from dynamic library!');
        })

      Text(this.result)

      Button('清理库')
        .onClick(() => {
          aki.CleanupLibrary();
        })
    }
  }
}

📚 实际案例:HMAC-SHA256预编译库移植

案例1:Node-API方式集成预编译HMAC-SHA256库

1. 准备预编译的HMAC-SHA256库
# 预编译库文件
libs/
├── arm64/
│   └── libhmac_sha256.so
└── x86_64/
    └── libhmac_sha256.so

# 头文件
include/
└── hmac_sha256.h
// include/hmac_sha256.h
#ifndef HMAC_SHA256_H
#define HMAC_SHA256_H

#ifdef __cplusplus
extern "C" {
#endif

// HMAC-SHA256计算函数
size_t hmac_sha256(
    const void* key, const size_t keylen,
    const void* data, const size_t datalen,
    void* out, const size_t outlen
);

// 十六进制字符串转换函数
void bytes_to_hex(const void* data, size_t len, char* hex);

#ifdef __cplusplus
}
#endif

#endif // HMAC_SHA256_H
2. 实现Node-API包装器
// napi_init.cpp
#include <napi.h>
#include "hmac_sha256.h"
#include <string>
#include <vector>

static napi_value HmacSha256Hash(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    // 获取参数
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 获取key和data字符串
    char key[256], data[1024];
    size_t key_len, data_len;
    napi_get_value_string_utf8(env, args[0], key, sizeof(key), &key_len);
    napi_get_value_string_utf8(env, args[1], data, sizeof(data), &data_len);

    // 计算HMAC-SHA256
    std::vector<uint8_t> output(32);
    size_t result_len = hmac_sha256(key, key_len, data, data_len, output.data(), output.size());

    // 返回Buffer
    void* buffer_data;
    napi_value result_buffer;
    napi_create_arraybuffer(env, result_len, &buffer_data, &result_buffer);
    memcpy(buffer_data, output.data(), result_len);

    return result_buffer;
}

static napi_value HmacSha256Hex(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    // 获取参数
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 获取key和data字符串
    char key[256], data[1024];
    size_t key_len, data_len;
    napi_get_value_string_utf8(env, args[0], key, sizeof(key), &key_len);
    napi_get_value_string_utf8(env, args[1], data, sizeof(data), &data_len);

    // 计算HMAC-SHA256
    std::vector<uint8_t> output(32);
    size_t result_len = hmac_sha256(key, key_len, data, data_len, output.data(), output.size());

    // 转换为十六进制字符串
    char hex[65];
    bytes_to_hex(output.data(), result_len, hex);
    hex[64] = '\0';

    // 返回字符串
    napi_value result_string;
    napi_create_string_utf8(env, hex, NAPI_AUTO_LENGTH, &result_string);
    return result_string;
}

// 模块初始化
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"hmacSha256Hash", nullptr, HmacSha256Hash, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"hmacSha256Hex", nullptr, HmacSha256Hex, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}

// 模块注册
static napi_module hmacModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterHmacModule() { 
    napi_module_register(&hmacModule);
}
3. 配置CMakeLists.txt
# CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(HmacSha256Wrapper)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

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

# 添加名为entry的库
add_library(entry SHARED napi_init.cpp)

# 链接预编译的HMAC-SHA256库
target_link_libraries(entry PUBLIC 
    ${NATIVERENDER_ROOT_PATH}/../../../libs/${OHOS_ARCH}/libhmac_sha256.so
    libace_napi.z.so
)

案例2:AKI方式集成预编译HMAC-SHA256库

// hmac_sha256_aki.cpp
#include <aki/jsbind.h>
#include <aki/array_buffer.h>
#include "hmac_sha256.h"
#include <string>
#include <vector>

// 字符串输入版本
std::vector<uint8_t> HmacSha256Hash(const std::string& key, const std::string& data) {
    std::vector<uint8_t> output(32);
    size_t result_len = hmac_sha256(key.c_str(), key.length(), data.c_str(), data.length(), output.data(), output.size());
    output.resize(result_len);
    return output;
}

// ArrayBuffer输入版本
aki::ArrayBuffer HmacSha256HashBuffer(const aki::ArrayBuffer& key, const aki::ArrayBuffer& data) {
    std::vector<uint8_t> output(32);
    size_t result_len = hmac_sha256(key.GetData(), key.GetLength(), data.GetData(), data.GetLength(), output.data(), output.size());
    aki::ArrayBuffer result(output.data(), result_len);
    return result;
}

// 十六进制输出版本
std::string HmacSha256Hex(const std::string& key, const std::string& data) {
    std::vector<uint8_t> output(32);
    size_t result_len = hmac_sha256(key.c_str(), key.length(), data.c_str(), data.length(), output.data(), output.size());
    
    char hex[65];
    bytes_to_hex(output.data(), result_len, hex);
    hex[64] = '\0';
    
    return std::string(hex);
}

// 注册AKI插件
JSBIND_ADDON(hmac_sha256_aki)

// 注册全局函数
JSBIND_GLOBAL() {
    JSBIND_FUNCTION(HmacSha256Hash);
    JSBIND_FUNCTION(HmacSha256HashBuffer);
    JSBIND_FUNCTION(HmacSha256Hex);
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(hmac_sha256_aki)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置AKI根路径
set(AKI_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules/@ohos/aki)
set(CMAKE_MODULE_PATH ${AKI_ROOT_PATH})
find_package(Aki REQUIRED)

# 源文件
set(SOURCES
    hmac_sha256_aki.cpp
)

# 创建共享库
add_library(hmac_sha256_aki SHARED ${SOURCES})

# 链接AKI库和预编译的HMAC-SHA256库
target_link_libraries(hmac_sha256_aki PUBLIC 
    Aki::libjsbind
    ${CMAKE_CURRENT_SOURCE_DIR}/../libs/${OHOS_ARCH}/libhmac_sha256.so
)

# 包含头文件路径
target_include_directories(hmac_sha256_aki PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../include
)

# 设置输出目录
set_target_properties(hmac_sha256_aki PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib
)

✅ 最佳实践

1. 预编译库的设计原则

  • C语言接口: 使用extern "C"包装,确保ABI兼容性
  • 版本管理: 提供版本信息和兼容性检查
  • 错误处理: 提供完善的错误码和错误信息
  • 内存管理: 明确内存所有权和生命周期

2. 集成方式选择

  • 静态链接: 适用于小型库,编译时链接
  • 动态链接: 适用于大型库,运行时加载
  • 插件化: 适用于可扩展的库,支持热插拔

3. 性能优化

  • 延迟加载: 只在需要时加载库
  • 缓存机制: 缓存函数指针,避免重复查找
  • 内存池: 使用内存池减少内存分配开销

4. 错误处理

// 错误处理示例
static napi_value SafeCallLibrary(napi_env env, napi_callback_info info) {
    try {
        // 调用库函数
        return YourFunction(env, info);
    } catch (const std::exception& e) {
        napi_throw_error(env, nullptr, e.what());
        return nullptr;
    } catch (...) {
        napi_throw_error(env, nullptr, "Unknown error occurred");
        return nullptr;
    }
}

❓ 常见问题

Q1: 如何处理不同架构的so库?

A: 在CMakeLists.txt中使用条件判断:

if(OHOS_ARCH STREQUAL "arm64")
    set(LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../libs/arm64")
elseif(OHOS_ARCH STREQUAL "x86_64")
    set(LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../libs/x86_64")
endif()

target_link_libraries(your_library PUBLIC ${LIB_PATH}/libyour_library.so)

Q2: 如何处理so库的依赖问题?

A: 确保所有依赖的so库都在正确的路径下,并在CMakeLists.txt中正确链接:

target_link_libraries(your_library PUBLIC 
    ${LIB_PATH}/libyour_library.so
    ${LIB_PATH}/libdependency1.so
    ${LIB_PATH}/libdependency2.so
)

Q3: 如何处理so库的版本兼容性?

A: 在库中提供版本检查函数:

// 版本检查
int get_library_version() {
    return 100; // 版本号
}

// 兼容性检查
bool check_compatibility(int required_version) {
    return get_library_version() >= required_version;
}

Q4: 如何处理so库的加载失败?

A: 提供完善的错误处理和回退机制:

bool LoadLibrarySafely(const std::string& path) {
    void* handle = dlopen(path.c_str(), RTLD_LAZY);
    if (!handle) {
        console.error("Failed to load library:", dlerror());
        return false;
    }
    
    // 验证库的完整性
    if (!ValidateLibrary(handle)) {
        dlclose(handle);
        return false;
    }
    
    return true;
}

Q5: 如何在纯ArkTS工程中使用预编译so库?

A: 使用模块动态依赖的方式,确保so库已经适配了Native接口:

// oh-package.json5
{
  "dependencies": {
    "libyour_library.so": "file:./src/main/cpp/types/libyour_library"
  }
}

🎉 总结

预编译so库的集成方式为HarmonyOS应用开发提供了更大的灵活性:

  • 模块化开发: 支持团队协作和代码复用
  • 性能优化: 避免重复编译,提高构建效率
  • 版本管理: 支持独立的版本控制和兼容性管理
  • 多种集成方式: 支持静态链接、动态链接和插件化集成

选择合适的集成方式取决于具体的项目需求和约束条件。无论选择哪种方式,都要确保:

  1. 接口设计合理: 提供ArkTS友好的接口
  2. 错误处理完善: 提供详细的错误信息和回退机制
  3. 性能优化: 减少不必要的开销
  4. 兼容性保证: 确保不同版本和架构的兼容性

参考链接

https://gitcode.com/openharmony-sig/tpc_c_cplusplus/blob/39de4c9403b60f14c4352d20c32934a983936956/lycium/doc/ohos_use_sdk/OHOS_SDK-Usage.md

https://gitcode.com/openharmony-sig/tpc_c_cplusplus/blob/39de4c9403b60f14c4352d20c32934a983936956/lycium/doc/app_calls_third_lib.md

https://gitcode.com/openharmony-sig/tpc_c_cplusplus/tree/39de4c9403b60f14c4352d20c32934a983936956/lycium

https://gitcode.com/openharmony-sig/tpc_c_cplusplus/blob/39de4c9403b60f14c4352d20c32934a983936956/lycium/Buildtools/README.md

https://gitcode.com/openharmony-sig/tpc_c_cplusplus

https://gitcode.com/openharmony-sig/tpc_c_cplusplus/blob/master/docs/adapter_windows.md

https://gitee.com/javen678/hello-ohos-napi/blob/master/doc/README.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值