当前位置: 首页 > news >正文

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

在这里插入图片描述

类型映射关系

在这里插入图片描述

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 <aki/jsbind.h>
#include <aki/array_buffer.h>
#include "your_library.h"  // 你的C/C++库头文件
#include <string>
#include <vector>// 用户自定义业务函数
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(SOURCESyour_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 PROPERTIESLIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../libRUNTIME_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
@Component
struct 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代码与业务代码,我们讨厌 毫无边界性的编程。
在这里插入图片描述

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.cpp
EXTERN_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.cpp
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);// 调用你的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.ts
export 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.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 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
@Component
struct 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.h
size_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 <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) {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 <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-SHA256std::vector<uint8_t> output(32);size_t result_len = hmac_sha256(key, key_len, data, data_len, output.data(), output.size());// 返回Buffervoid* 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
@Component
struct 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
@Component
struct 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

http://www.dtcms.com/a/344510.html

相关文章:

  • 2025年推理大模型有哪些以及优势对比
  • C++函数重载与引用详解
  • 线段树01
  • 合同差异智能比对,有效规避“阴阳合同”
  • 白名单过滤的文件上传如何bypass:boot2root靶机之fristileaks
  • 基于 SkyWalking + Elasticsearch + Grafana 的可落地调用链监控方案
  • 易混淆的CommonJS和ESM(ES Module)及它们区别
  • 工控/医疗设备没有连接网络,贝锐向日葵Q1破解远程运维难题
  • 【ElasticSearch】IK分词器安装,配置修改,支持新增词组,中文常用mapping使用案例
  • Python 中 SQLAlchemy 和 MySQLdb 的关系
  • MongoDB 分片集群把非分片集合转成分片集合
  • MySQL 错误码
  • Flutter Provider 详解:从状态管理痛点到实战落地
  • Linux权限详解
  • 电子基石:硬件工程师的器件手册 (十三) - 电源管理IC:能量供给的艺术
  • 使用html+css+javascript练习项目布局--创建导航栏
  • 高并发场景数据与一致性的简单思考
  • 理解音频响度:LUFS 标准及其计算实现
  • 在灵码中配置MCP服务
  • Basic Threejs (2)
  • Unity中国小游戏行业沙龙:抖音小游戏平台分析与规划
  • Excel处理控件Aspose.Cells教程:使用Python将 Excel 转换为 NumPy
  • AWS OpenSearch 是什么
  • 复合设计模式
  • 阿里云详解:与 AWS、GCP 的全方位比较
  • openEuler系统中home文件夹下huawei、HwHiAiUser、lost+found 文件夹的区别和作用
  • 农业-学习记录
  • vue中监听页面滚动位置
  • Playwright进阶指南 (5):拦截与模拟网络请求
  • 【LLMs篇】19:vLLM推理中的KV Cache技术全解析