C/C++三方库移植到HarmonyOS平台详细教程
📋 目录
- 移植概述
- 移植方式选择
- AKI方式移植(推荐)
- Node-API方式移植
- 构建配置
- 实际案例:HMAC-SHA256库移植
- 最佳实践
- 常见问题
移植概述
什么是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的优势
- 极简使用: 解耦FFI代码与业务代码,友好的边界性编程体验
- 完整特性: 提供完整的数据类型转换、函数绑定、对象绑定、线程安全等特性
- 所见即所得: 一行代码完成ArkTS与C/C++的无障碍跨语言互调
- 无需关心底层: 开发者无需关心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: 检查以下几点:
- 确保安装了AKI依赖:
ohpm install @ohos/aki
- 检查CMake版本:
cmake --version
- 检查HarmonyOS SDK路径配置
- 查看构建日志定位具体错误
🎉 总结
C/C++三方库移植到HarmonyOS平台是一个系统性的工程,选择合适的移植方式至关重要:
- AKI方式: 推荐用于大多数场景,开发效率高
- Node-API方式: 适用于需要精确控制的特殊场景
通过本教程的学习,您应该能够:
- 理解移植的基本概念和流程。
- 选择合适的移植方式。
- 使用AKI或Node-API实现库的移植。
- 配置构建环境并生成可用的库。
- 在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