HarmonyOS鸿蒙Next中如何将arkts侧大量字节数组(3840*2160*3)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧

HarmonyOS鸿蒙Next中如何将arkts侧大量字节数组(384021603)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧 如何将arkts侧大量字节数组(384021603)传递到napi侧,调用C++算法,再将经计算后的该数组返回arkts侧

7 回复

核心方案:使用 ArrayBuffer 与零拷贝机制

鸿蒙文档明确指出,处理大型二进制数据时,应优先使用 ArrayBufferTypedArray(如 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_infonapi_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 分配新内存并填充结果,然后返回。

重要注意事项(来自文档)

  1. 性能优先:对于您所述的大尺寸数据,强烈推荐使用零拷贝的 napi_create_external_arraybuffer 或原地修改方案,以避免巨大的内存拷贝开销。
  2. 内存管理
    • 使用 napi_create_external_arraybuffer 时,可以提供一个 FinalizeCallback。当ArkTS的 ArrayBuffer 被垃圾回收时,会调用此回调,以便C++侧释放相关资源(如果内存是C++分配的)。
    • 如果内存由ArkTS管理(最常见的情况),则无需此回调,但C++侧必须确保在回调函数执行期间不访问已可能被释放的内存。
  3. 错误处理:始终在C++侧检查 napi_status 返回值,并在出错时使用 napi_throw_error 抛出异常到ArkTS侧。
  4. 替代方案:Buffer对象:文档也提到了 Buffer 相关接口(如 napi_create_buffer),但 ArrayBuffer 是更现代和标准的处理二进制数据的方式,与 TypedArray 兼容性更好,应作为首选

总结

根据鸿蒙文档,您的问题的解决方案是:

  1. ArkTS侧:将大型字节数组包装在 ArrayBuffer(通过 Uint8Array)中。
  2. Node-API (C++)侧
    • 高性能方案:使用 napi_get_arraybuffer_info 获取传入 ArrayBuffer 的指针和长度,原地进行算法计算,然后返回原 ArrayBuffer 或一个新的 ArrayBuffer(根据算法是否需要改变数据大小)。
    • 安全方案:使用 napi_get_arraybuffer_infonapi_create_arraybuffer 创建数据的副本,在副本上进行计算,然后返回新的 ArrayBuffer
  3. 关键:优先选择原地修改的零拷贝方案以应对您的大数据量场景,并妥善处理内存生命周期。

更多关于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调用结束。

回到顶部