C/C++三方库移植到鸿蒙HarmonyOS平台详细教程
📋 目录
移植概述
什么是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/aki@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(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
@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-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
@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-sig/aki
https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ndk-33
更多推荐
所有评论(0)