> 技术文档 > C/C++三方库移植到HarmonyOS平台详细教程

C/C++三方库移植到HarmonyOS平台详细教程


📋 目录

  1. 移植概述
  2. 移植方式选择
  3. AKI方式移植(推荐)
  4. Node-API方式移植
  5. 构建配置
  6. 实际案例:HMAC-SHA256库移植
  7. 最佳实践
  8. 常见问题

移植概述

什么是C/C++三方库移植?

C/C++三方库移植是指将现有的C/C++库适配到HarmonyOS平台,使其能够在ArkTS应用中调用。移植的主要目的是:

  • 性能优化: C/C++库通常比JS/TS库运行效率更高
  • 功能复用: 利用成熟的C/C++库,避免重复开发
  • 生态扩展: 丰富HarmonyOS的第三方库生态

移植的基本流程

1. 库分析 → 2. 移植方式选择 → 3. 接口设计 → 4. 代码实现 → 5. 构建配置 → 6. 测试验证

移植方式选择

两种主要方式对比

特性 AKI方式 Node-API方式 代码复杂度 低 高 开发效率 高 低 学习成本 低 高 性能 优秀 优秀 维护成本 低 高 推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐

选择建议

  • 推荐AKI方式: 适用于大多数场景,开发效率高
  • Node-API方式: 适用于需要精确控制或特殊需求的场景

🚀 AKI方式移植(推荐)

什么是AKI?

AKI (Alpha Kernel Interacting) 是一款边界性编程体验友好的ArkTS FFI开发框架,提供极简语法糖使用方式,一行代码完成ArkTS与C/C++的无障碍跨语言互调。

AKI的优势

  1. 极简使用: 解耦FFI代码与业务代码,友好的边界性编程体验
  2. 完整特性: 提供完整的数据类型转换、函数绑定、对象绑定、线程安全等特性
  3. 所见即所得: 一行代码完成ArkTS与C/C++的无障碍跨语言互调
  4. 无需关心底层: 开发者无需关心Node-API的线程安全问题、Native对象GC问题

AKI是一款专为鸿蒙原生开发设计的FFI(外部函数接口)开发框架。它极大地简化了JS与C/C++之间的跨语言访问,为开发者提供了一种边界性编程体验友好的解决方案。通过AKI,开发者可以使用让代码更易读的语法糖,实现JS与C/C++之间的无障碍跨语言互调,真正做到所“键”即所得。

这一创新框架的出现,正是为了解决开发者在迁移C/C++项目到HarmonyOS NEXT时面临的核心痛点。传统的NAPI接口调用复杂,学习成本高,开发者需要耗费大量精力进行适配和迁移。AKI通过封装复杂的NAPI接口,让开发者无需直接接触繁琐的跨语言调用技术细节,这一设计不仅能有效减少跨语言调用接口90%的代码量,还能将跨语言调用接口和业务代码完全解耦,帮助开发者更加专注于产品创新与功能迭代,而非技术迁移的细节问题,大幅提升开发效率。

据悉,在涉及C/C++/ETS跨越语言调用的鸿蒙化应用中,有超过80%的项目都在使用AKI,如某知名购物应用,使用后减少了项目10%代码量;某知名社交电商平台使用后减少了50%以上跨语言调用接口代码量;某图像处理软件所有C++代码复用通过AKI来实现。使用AKI后这些项目不仅减少了项目代码量,还显著优化了代码复用与迁移流程。

OHPM仓AKI直达地址:https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faki

C/C++三方库移植到HarmonyOS平台详细教程

类型映射关系

C/C++三方库移植到HarmonyOS平台详细教程

AKI移植步骤

1. 环境准备
# 确保开发环境满足要求- HarmonyOS SDK 4.0+- Node.js 16+- CMake 3.4.1+
2. 安装AKI依赖
# 进入项目entry目录cd entry# 安装AKI依赖ohpm install @ohos/aki
3. 创建C++包装器
// example_aki.cpp#include #include #include \"your_library.h\" // 你的C/C++库头文件#include #include // 用户自定义业务函数std::string YourFunction(const std::string& input) { // 调用你的C/C++库函数 return your_library_function(input.c_str());}// 支持ArrayBuffer的函数aki::ArrayBuffer YourBufferFunction(const aki::ArrayBuffer& input) { // 处理二进制数据 std::vector<uint8_t> result = 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);}
4. 创建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 your_library.c # 你的C/C++库源文件 your_library_wrapper.c # 包装器源文件 example_aki.cpp # AKI包装器)# 创建共享库add_library(your_library_aki SHARED ${SOURCES})# 链接AKI库target_link_libraries(your_library_aki PUBLIC Aki::libjsbind)# 设置输出目录set_target_properties(your_library_aki PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib)
5. 创建TypeScript类型定义
// types/libyour_library_aki/index.d.ts/** * 你的库函数 * @param input - 输入字符串 * @returns 处理结果 */export function YourFunction(input: string): string;/** * 处理ArrayBuffer的函数 * @param input - 输入ArrayBuffer * @returns 处理结果ArrayBuffer */export function YourBufferFunction(input: ArrayBuffer): ArrayBuffer;
6. 在ArkTS中使用
// 在你的ArkTS文件中import aki from \'libyour_library_aki.so\'@Entry@Componentstruct MyPage { @State result: string = \'\'; build() { Column() { Button(\'调用C++库\') .onClick(() => { // 一行代码调用C++函数! this.result = aki.YourFunction(\'Hello from ArkTS!\'); }) Text(this.result) } }}

🔧 Node-API方式移植

Node-API方式特点

HarmonyOS Node-API是基于Node.js 12.x LTS的Node-API规范扩展开发的机制,为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。它提供了一组稳定的、跨平台的API,可以在不同的操作系统上使用。

  • 精确控制: 可以精确控制每个细节
  • 灵活性高: 支持复杂的参数处理和错误处理
  • 学习成本高: 需要深入理解Node-API机制

开发者使用NAPI过程中还会发现:为了做跨线程任务,需要做线程管理,需要关心环境上下文;为了使用结构体对象,需要关注napi_value生命周期如何管理;巴拉巴拉等等与自己业务无关的逻辑。搞了半天,发现业务代码一行没写,还在写NAPI的跨语言调用实现。拥有洁癖的开发者还会发现,很难做到隔离NAPI代码与业务代码,我们讨厌 毫无边界性的编程。
C/C++三方库移植到HarmonyOS平台详细教程

Node-API移植步骤

1. 创建Native C++工程

在DevEco Studio中New > Create Project,选择Native C++模板,创建新工程。

2. 设置模块注册信息
// entry/src/main/cpp/napi_init.cpp// 准备模块加载相关信息static napi_module demoModule = { .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr, .nm_register_func = Init, .nm_modname = \"entry\", // 模块名称,对应so库名称 .nm_priv = ((void*)0), .reserved = {0},};// 加载so时,该函数会自动被调用,将模块注册到系统中extern \"C\" __attribute__((constructor)) void RegisterDemoModule() { napi_module_register(&demoModule);}
3. 模块初始化
// entry/src/main/cpp/napi_init.cppEXTERN_C_START// 模块初始化static napi_value Init(napi_env env, napi_value exports) { // ArkTS接口与C++接口的绑定和映射 napi_property_descriptor desc[] = { {\"yourFunction\", nullptr, YourFunction, nullptr, nullptr, nullptr, napi_default, nullptr}, {\"yourBufferFunction\", nullptr, YourBufferFunction, nullptr, nullptr, nullptr, napi_default, nullptr} }; // 在exports对象上挂载Native方法 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); return exports;}EXTERN_C_END
4. 实现Native侧函数
// entry/src/main/cpp/napi_init.cppstatic 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); // 调用你的C/C++库函数 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 YourBufferFunction(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); // 获取ArrayBuffer参数 void* buffer_data; size_t buffer_length; napi_get_arraybuffer_info(env, args[0], &buffer_data, &buffer_length); // 处理二进制数据 std::vector<uint8_t> result = process_buffer(buffer_data, buffer_length); // 返回ArrayBuffer结果 napi_value result_buffer; napi_create_arraybuffer(env, result.size(), &buffer_data, &result_buffer); memcpy(buffer_data, result.data(), result.size()); return result_buffer;}
5. 创建TypeScript类型定义
// entry/src/main/cpp/types/libentry/index.d.tsexport const yourFunction: (input: string) => string;export const yourBufferFunction: (input: ArrayBuffer) => ArrayBuffer;
6. 配置oh-package.json5
// entry/src/main/cpp/types/libentry/oh-package.json5{ \"name\": \"libentry.so\", \"types\": \"./index.d.ts\", \"version\": \"\", \"description\": \"Please describe the basic information.\"}
7. 配置CMakeLists.txt
# entry/src/main/cpp/CMakeLists.txtcmake_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 your_library.c)# 构建此可执行文件需要链接的库target_link_libraries(entry PUBLIC libace_napi.z.so)
8. 在ArkTS中使用
// entry/src/main/ets/pages/Index.ets// 通过import的方式,引入Native能力import nativeModule from \'libentry.so\'@Entry@Componentstruct Index { @State result: string = \'\'; build() { Row() { Column() { Text(\'调用C++库\') .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { this.result = nativeModule.yourFunction(\'Hello from ArkTS!\'); }) Text(this.result) .fontSize(30) } .width(\'100%\') } .height(\'100%\') }}

Node-API的约束限制

SO命名规则

导入使用的模块名和注册时的模块名大小写保持一致,如模块名为entry,则so的名字为libentry.so,napi_module中nm_modname字段应为entry,ArkTS侧使用时写作:import xxx from ‘libentry.so’。

注册建议
  • nm_register_func对应的函数需要加上static,防止与其他so里的符号冲突
  • 模块注册的入口函数名需要确保不与其它模块重复
多线程限制
  • Node-API接口只能在JS线程使用
  • Native接口入参env与特定JS线程绑定只能在创建时的线程使用
  • 使用Node-API接口创建的数据需在env完全销毁前进行释放,避免内存泄漏

🏗️ 构建配置

项目结构

your_library_harmonyos/├── CMakeLists.txt  # CMake构建配置├── your_library.c  # 原始C/C++库源文件├── your_library.h  # 原始C/C++库头文件├── example_aki.cpp  # AKI包装器(推荐)├── napi_init.cpp  # Node-API包装器├── package.json# 包配置├── types/│ └── libyour_library_aki/│ └── index.d.ts # TypeScript类型定义├── examples/│ └── arkts_example.ets # ArkTS使用示例└── README.md # 说明文档

package.json配置

{ \"name\": \"your-library-harmonyos\", \"version\": \"1.0.0\", \"description\": \"Your C/C++ library for HarmonyOS\", \"main\": \"lib/index.js\", \"types\": \"types/libyour_library_aki/index.d.ts\", \"scripts\": { \"build\": \"cmake -B build && cmake --build build\", \"clean\": \"rm -rf build lib\", \"test\": \"node test/test.js\" }, \"keywords\": [ \"harmonyos\", \"arkts\", \"aki\", \"napi\" ], \"dependencies\": { \"@ohos/aki\": \"^1.0.0\" }, \"devDependencies\": { \"@types/node\": \"^18.0.0\" }}

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

原始仓库地址:https://github.com/h5p9sl/hmac_sha256

原始C库分析

// hmac_sha256.hsize_t hmac_sha256( const void* key, const size_t keylen, const void* data, const size_t datalen, void* out, const size_t outlen);

AKI方式实现

// hmac_sha256_aki.cpp#include #include #include \"../hmac_sha256.h\"#include #include // 字符串输入版本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) { auto hash = HmacSha256Hash(key, data); aki::ArrayBuffer buffer(hash.data(), hash.size()); return ArrayBufferToHex(buffer);}// 注册AKI插件JSBIND_ADDON(hmac_sha256_aki)// 注册全局函数JSBIND_GLOBAL() { JSBIND_FUNCTION(HmacSha256Hash); JSBIND_FUNCTION(HmacSha256HashBuffer); JSBIND_FUNCTION(HmacSha256Hex);}

Node-API方式实现

// napi_init.cpp#include #include \"../hmac_sha256.h\"#include #include 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 Init(napi_env env, napi_value exports) { napi_property_descriptor desc[] = { {\"hmacSha256Hash\", nullptr, HmacSha256Hash, 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 = \"hmac_sha256\", .nm_priv = ((void*)0), .reserved = {0},};extern \"C\" __attribute__((constructor)) void RegisterHmacModule() { napi_module_register(&hmacModule);}

在ArkTS中使用

// AKI方式import aki from \'libhmac_sha256_aki.so\'@Entry@Componentstruct HmacExample { @State result: string = \'\'; build() { Column() { Button(\'计算 HMAC-SHA256\') .onClick(() => { // 一行代码完成HMAC计算! this.result = aki.HmacSha256Hex(\'my-key\', \'Hello World!\'); }) Text(this.result) } }}// Node-API方式import nativeModule from \'libhmac_sha256.so\'@Entry@Componentstruct HmacExample { @State result: string = \'\'; build() { Column() { Button(\'计算 HMAC-SHA256\') .onClick(() => { const hash = nativeModule.hmacSha256Hash(\'my-key\', \'Hello World!\'); this.result = Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, \'0\')) .join(\'\'); }) Text(this.result) } }}

✅ 最佳实践

1. 移植前准备

  • 库分析: 仔细分析原始库的API和依赖关系
  • 功能规划: 确定需要移植的核心功能
  • 接口设计: 设计ArkTS友好的接口

2. 代码组织

  • 模块化: 将不同功能模块分离
  • 错误处理: 提供完善的错误处理机制
  • 类型安全: 使用TypeScript提供类型安全

3. 性能优化

  • 内存管理: 注意内存泄漏问题
  • 数据转换: 减少不必要的数据转换
  • 批量处理: 支持批量操作提高效率

4. 测试验证

  • 单元测试: 编写完整的单元测试
  • 集成测试: 在真实环境中测试
  • 性能测试: 验证性能表现

❓ 常见问题

Q1: 如何选择移植方式?

A: 推荐使用AKI方式,除非有特殊需求。AKI方式代码量少、开发效率高、维护成本低。

Q2: Node-API和Node.js的Node-API有什么区别?

A: HarmonyOS Node-API是基于Node.js 12.x LTS的Node-API规范扩展开发的机制,提供了ArkTS/JS与C/C++模块之间的交互能力,但有一些HarmonyOS特有的扩展接口。

Q3: 如何处理复杂的C++类?

A: 使用AKI的JSBIND_CLASS语法糖绑定C++类:

class MyClass {public: MyClass(int value) : value_(value) {} int getValue() const { return value_; } void setValue(int value) { value_ = value; }private: int value_;};JSBIND_CLASS(MyClass) { JSBIND_CONSTRUCTOR<int>(); JSBIND_METHOD(getValue); JSBIND_METHOD(setValue);}

Q4: 如何处理异步操作?

A: 使用Node-API的异步工作接口:

// 创建异步工作napi_async_work work;napi_create_async_work(env, nullptr, resource_name, execute_callback, complete_callback, data, &work);napi_queue_async_work(env, work);

Q5: 构建失败怎么办?

A: 检查以下几点:

  1. 确保安装了AKI依赖:ohpm install @ohos/aki
  2. 检查CMake版本:cmake --version
  3. 检查HarmonyOS SDK路径配置
  4. 查看构建日志定位具体错误

🎉 总结

C/C++三方库移植到HarmonyOS平台是一个系统性的工程,选择合适的移植方式至关重要:

  • AKI方式: 推荐用于大多数场景,开发效率高
  • Node-API方式: 适用于需要精确控制的特殊场景

通过本教程的学习,您应该能够:

  1. 理解移植的基本概念和流程。
  2. 选择合适的移植方式。
  3. 使用AKI或Node-API实现库的移植。
  4. 配置构建环境并生成可用的库。
  5. 在ArkTS应用中正确使用移植的库。

如果还有其他问题,欢迎关注交流。

作者:csdn猫哥,个人博客:blog.csdn.net/yyz_1987,转载请注明出处。

参考链接

https://gitcode.com/openharmony-tpc/docs/blob/master/contribute/adapter-guide/c_c%2B%2B%E7%A7%BB%E6%A4%8D%E9%80%82%E9%85%8D%E6%8C%87%E5%AF%BC.md

https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-dynamic-link-library#section166546365376

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

https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ndk-33

https://blog.itpub.net/70011554/viewspace-2968815/

https://www.woshipm.com/share/6192541.html