HarmonyOS鸿蒙Next中如何将arkts侧大量字节数组(3840*2160*3)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧
HarmonyOS鸿蒙Next中如何将arkts侧大量字节数组(384021603)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧 如何将arkts侧大量字节数组(384021603)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧
核心方案:使用 ArrayBuffer
与零拷贝机制
鸿蒙文档明确指出,处理大型二进制数据时,应优先使用 ArrayBuffer 和 TypedArray(如 Uint8Array
),并通过 外部缓冲区(External Buffer) 或 转移(Transfer) 机制避免不必要的内存拷贝,以最大化性能。
1. 在ArkTS侧准备数据
在ArkTS中,您的大型数据应封装在 ArrayBuffer
中,通常通过 TypedArray
(如 Uint8Array
)进行视图操作。
// ArkTS 侧:创建或获取数据
let imageData: Uint8Array = new Uint8Array(3840 * 2160 * 3); // 您的数据源
let arrayBuffer: ArrayBuffer = imageData.buffer; // 获取底层的ArrayBuffer
2. 将ArrayBuffer从ArkTS传递到C++ (Node-API)
方法一(推荐:零拷贝):使用 napi_create_external_arraybuffer
此接口允许C++侧直接操作ArkTS ArrayBuffer
的底层内存,无需拷贝,性能最高。但需注意内存生命周期管理(C++侧需确保内存有效,直到ArkTS不再使用)。
- C++ 侧代码示例:
#include "napi/native_api.h"
// 假设这是您的C++算法函数
void ProcessImageData(void* data, size_t length) {
// 直接在此处对 data 指针指向的内存进行计算
// ... 您的算法逻辑 ...
}
static napi_value ProcessImage(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);
// 1. 检查传入的是否是ArrayBuffer
bool is_arraybuffer;
napi_is_arraybuffer(env, args[0], &is_arraybuffer);
if (!is_arraybuffer) {
napi_throw_error(env, nullptr, "Argument must be an ArrayBuffer");
return nullptr;
}
// 2. 获取ArrayBuffer的信息(数据指针和长度)
void* data = nullptr;
size_t length = 0;
napi_get_arraybuffer_info(env, args[0], &data, &length);
// 3. 调用C++算法处理数据(零拷贝)
ProcessImageData(data, length); // 原地修改数据
// 4. 返回修改后的ArrayBuffer(可选,因为是原地修改)
return args[0];
}
- ArkTS 接口声明 (index.d.ts):
export const processImage: (buffer: ArrayBuffer) => ArrayBuffer;
- ArkTS 侧调用:
import testNapi from 'libentry.so';
// ... 获取 arrayBuffer ...
let resultBuffer = testNapi.processImage(arrayBuffer); // 接收处理后的ArrayBuffer
let resultData = new Uint8Array(resultBuffer);
方法二(安全拷贝):使用 napi_get_arraybuffer_info
与 napi_create_arraybuffer
如果需要在C++侧创建数据的独立副本进行计算,可以使用此方法。这会增加一次内存拷贝,开销较大,但数据隔离更安全。
- C++ 侧代码示例(部分):
// ... 获取传入的ArrayBuffer info ...
napi_get_arraybuffer_info(env, args[0], &originalData, &length);
// 创建新的ArrayBuffer用于存放副本
void* copiedData = nullptr;
napi_value newArrayBuffer;
napi_create_arraybuffer(env, length, &copiedData, &newArrayBuffer);
// 拷贝数据
memcpy(copiedData, originalData, length);
// 在副本上执行算法 ProcessImageData(copiedData, length);
// 返回新的ArrayBuffer
return newArrayBuffer;
3. 将处理后的数据从C++返回ArkTS
- 如果采用“方法一(零拷贝)”:由于是原地修改,直接返回传入的
ArrayBuffer
即可。 - 如果采用“方法二(拷贝)”:返回新创建的
napi_value newArrayBuffer
。 - 如果算法产生全新数据:在C++侧使用
napi_create_arraybuffer
分配新内存并填充结果,然后返回。
重要注意事项(来自文档)
- 性能优先:对于您所述的大尺寸数据,强烈推荐使用零拷贝的
napi_create_external_arraybuffer
或原地修改方案,以避免巨大的内存拷贝开销。 - 内存管理:
- 使用
napi_create_external_arraybuffer
时,可以提供一个FinalizeCallback
。当ArkTS的ArrayBuffer
被垃圾回收时,会调用此回调,以便C++侧释放相关资源(如果内存是C++分配的)。 - 如果内存由ArkTS管理(最常见的情况),则无需此回调,但C++侧必须确保在回调函数执行期间不访问已可能被释放的内存。
- 使用
- 错误处理:始终在C++侧检查
napi_status
返回值,并在出错时使用napi_throw_error
抛出异常到ArkTS侧。 - 替代方案:Buffer对象:文档也提到了
Buffer
相关接口(如napi_create_buffer
),但ArrayBuffer
是更现代和标准的处理二进制数据的方式,与TypedArray
兼容性更好,应作为首选。
总结
根据鸿蒙文档,您的问题的解决方案是:
- ArkTS侧:将大型字节数组包装在
ArrayBuffer
(通过Uint8Array
)中。 - Node-API (C++)侧:
- 高性能方案:使用
napi_get_arraybuffer_info
获取传入ArrayBuffer
的指针和长度,原地进行算法计算,然后返回原ArrayBuffer
或一个新的ArrayBuffer
(根据算法是否需要改变数据大小)。 - 安全方案:使用
napi_get_arraybuffer_info
和napi_create_arraybuffer
创建数据的副本,在副本上进行计算,然后返回新的ArrayBuffer
。
- 高性能方案:使用
- 关键:优先选择原地修改的零拷贝方案以应对您的大数据量场景,并妥善处理内存生命周期。
更多关于HarmonyOS鸿蒙Next中如何将arkts侧大量字节数组(3840*2160*3)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
受教了!
【背景知识】
Node-API:Node-API是用于封装JavaScript能力为Native插件的API,独立于底层JavaScript,并作为Node.js的一部分。
【参考方案】:
可参考实现ArkTS侧基础类型转化为Native侧数据类型功能示例,实现了将ArkTS侧的数据(基础的数据类型)传递到Native侧进行处理并返回结果到ArkTS侧的效果。
- 场景一:int类型加法(int类型数据)。主要通过napi_get_value_int32()函数与napi_create_int32()函数、分别获取int型数据和构造int型数据。
- 场景二:string类型拼接(string类型数据和bool类型数据)。主要通过napi_get_value_string_utf8()、napi_get_value_bool()、以及napi_create_string_utf8()函数实现、与场景一用法类似。
- 场景三:数组逆序(arr类型数据)。主要使用napi_get_array_length()、napi_create_array_with_length()、napi_get_element()和napi_set_element()函数。与场景一二的区别是先获取数组长度、然后构建相应长度的数组。针对数组的每一项元素值通过napi_get_element()获取、每一项元素赋值时使用napi_set_element()实现。
- 场景四:arraybuffer求平方(arraybuffer类型数据)。主要使用napi_get_arraybuffer_info()、napi_create_external_arraybuffer()函数。区别在于napi_get_arraybuffer_info()获取的是底层缓冲区指针和指针长度。对于数据的修改需要利用reinterpret_cast将指针转化为uint型指针进行数据修改,修改后还需利用reinterpret_cast将指针转换回viod指针,该指针在napi_create_external_arraybuffer()构造新的arraybuffer时被引用。
- 场景五:object对象赋值(object类型)。主要使用napi_create_object()、napi_set_named_property()函数、区别在于需要先构造出object对象、然后利用napi_set_named_property()函数对object对象的某个属性进行赋值操作。
- 场景六:Date类型转换。主要使用napi_create_date()、napi_get_date_value()函数,创建、解析Date类型的对象。
实现步骤
1/ ArkTS侧数据准备与传递:
// 声明Native接口
// index.d.ts
export const processLargeData: (input: Uint8Array) => Uint8Array;
// 调用侧
import nativeModule from 'libentry.so';
// 创建大尺寸字节数组(示例)
let byteArray = new Uint8Array(3840 * 2160 * 3);
// 填充数据逻辑...
// 调用Native处理
let processedData = nativeModule.processLargeData(byteArray);
2/ Native C++侧数据处理:
#include <vector>
#include "napi_utils.h"
static napi_value ProcessLargeData(napi_env env, napi_callback_info info) {
// 获取输入数据
napi_value input;
napi_get_cb_info(env, info, nullptr, 1, &input, nullptr, nullptr);
// 解析Uint8Array
napi_typedarray_type type;
size_t length;
void* rawData;
napi_value arrayBuffer;
size_t byteOffset;
napi_get_typedarray_info(env, input, &type, &length, &rawData, &arrayBuffer, &byteOffset);
// 执行算法处理(这里举例简单反转操作)
auto* dataPtr = static_cast<uint8_t*>(rawData);
std::vector<uint8_t> processedVec(dataPtr, dataPtr + length);
for (auto& val : processedVec) {
val = 255 - val; // 示例算法逻辑
}
// 创建返回的ArrayBuffer
napi_value output;
void* outputBuffer;
napi_create_arraybuffer(env, length, &outputBuffer, &output);
memcpy(outputBuffer, processedVec.data(), length);
// 包装为Uint8Array
napi_value result;
napi_create_typedarray(env, napi_uint8_array, length, output, 0, &result);
return result;
}
在 HarmonyOS 中传递超大字节数组(384021603),核心是通过NAPI的ArrayBuffer实现内存共享(零拷贝),避免数据拷贝导致的性能损耗,具体步骤如下:
一、ArkTS 侧准备:封装数组为ArrayBuffer 并调用 NAPI
1.创建/转换ArrayBuffer:若字节数组是Uint8Array,直接通过其buffer属性获取ArrayBuffer(无需额外拷贝);若为其他类型数组,需先转为Uint8Array再取buffer。
2.调用NAPI 方法:通过@ohos.node或自定义 NAPI 模块,将ArrayBuffer和数组长度(固定为384021603=24883200)作为参数传入C++侧。
在HarmonyOS鸿蒙Next中,使用Node-API将ArkTS侧字节数组传递到C++侧,需通过napi_create_external_arraybuffer创建外部ArrayBuffer,避免数据拷贝。C++侧接收后可直接处理数据,处理完成后通过napi_get_arraybuffer_info获取返回。整个过程需确保内存管理正确,避免内存泄漏。
在HarmonyOS Next中,通过NAPI传递大型字节数组(如3840×2160×3)时,建议使用共享内存机制(如napi_create_arraybuffer
)避免数据拷贝开销。在ArkTS侧创建ArrayBuffer并传递到NAPI,C++侧通过指针直接操作内存,计算完成后同步返回同一内存区域。需注意线程安全和内存生命周期管理,确保ArkTS侧持有引用直至NAPI调用结束。