【HarmonyOS NEXT应用开发】案例70:跨语言的复杂参数类型传递

场景说明:

我们经常在ArkTS与C++之间相互传递参数,那么具体该如何传呢?下面介绍了几个常用的场景:

场景一:string类型传递

调用接口:

napi_get_value_string_utf8

实现能力:

通过 napi_get_value_string_utf8 获取字符串长度,然后根据长度将从 ArkTS 侧传过来的 napi_value 转换成字符串。

注意:

C++里字符串结尾是\0,所以转换成字符串时长度为stringSize + 1。

核心代码解释

Index.ets文件向C++层传递string数据。

let str:string = 'hello!';
testNapi.putString(str);

将value转成字符串返回,注意C++里字符串结尾是\0,所以转换成字符串时长度为stringSize + 1。

static std::string value2String(napi_env env, napi_value value) {
    size_t stringSize = 0;
    napi_get_value_string_utf8(env, value, nullptr, 0, &stringSize); // 获取字符串长度
    std::string valueString;
    valueString.resize(stringSize + 1);
    napi_get_value_string_utf8(env, value, &valueString[0], stringSize + 1, &stringSize); // 根据长度传换成字符串
    return valueString;
}

C++层获取string数据。

static napi_value ts_putString(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);
    napi_value str = args[0];//args[0]->string
​
    std::string stringValue = value2String(env, str);//将 str 转换成 string 类型
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "ts_putString str = %{public}s", stringValue.c_str());
​
    return nullptr;
}

实现效果

image.png

场景二:arraybuffer类型的传递

调用接口:

  • ArkTS传递给C++,解析ArrayBuffer

    napi_get_typedarray_info、napi_get_arraybuffer_info

  • C++传递给ArkTS,构建ArrayBuffer

    napi_create_arraybuffer、napi_create_typedarray

实现能力:

实现了 ArkTS 与 Native C++ 之间相互传递 arraybuffer。

Native C++ 侧接受传入的 ArkTS Array,通过 napi_get_typedarray_info 将获取到的数据传入数组 typedarray 生成 input_buffer ,然后通过 napi_get_arraybuffer_info 获取数组数据。

ArkTS 侧 接收 Native C++ 侧返回的 Array,通过 napi_create_arraybuffer 创建一个 arraybuffer 数组,根据创建的 arraybuffer 通过 napi_create_typedarray 创建一个 typedarray 并将 arraybuffer 存入 output_array,然后给 arraybuffer 赋值,最后返回 output_array。

核心代码解释

Index.ets

@Entry
@Component
struct Index {
  napiArray?:Int32Array
​
  build() {
    Row() {
      Column() {
        Button("NAPI2TS")//接收Native侧返回的Array
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.napiArray= testNapi.NAPI2TS()
            for(let num of this.napiArray) {
              console.info("NAPI2TS: JS   " + num.toString())
            }
          })
​
        Button("TS2NAPI")//向Native侧传入Array
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            if(this.napiArray){
              testNapi.TS2NAPI(this.napiArray)
              for(let num of this.napiArray) {
                console.info("TS2NAPI: JS   " + num.toString())
              }
            }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Native侧接受传入的 ArkTS Array

static napi_value TS2NAPI(napi_env env, napi_callback_info info) 
{
    // 获取TS层传来的参数
    size_t argc = 1;
    napi_value args;
    napi_get_cb_info(env, info, &argc, &args, NULL, NULL);
    napi_value input_array = args;
​
    // 获取传入数组typedarray生成input_buffer
    napi_typedarray_type type;// 数据类型
    napi_value input_buffer;
    size_t byte_offset;//数据偏移
    size_t i, length;//数据字节大小
    napi_get_typedarray_info(env, input_array, &type, &length, NULL, &input_buffer, &byte_offset);
​
    // 获取数组数据
    void *data;
    size_t byte_length;
    napi_get_arraybuffer_info(env, input_buffer, &data, &byte_length);
​
    // 遍历数组
    if (type == napi_int32_array) {
        int32_t *data_bytes = (int32_t *)(data);
        for (i = 0; i < length/sizeof(int32_t); i++) {
            OH_LOG_INFO(LOG_APP, "TS2NAPI: C++  %{public}d", *((int32_t *)(data_bytes) + i));
        }
    }
​
    return NULL;
}

TS侧接收 Native侧返回的Array。

// NAPI层 array 传到TS层
static napi_value NAPI2TS(napi_env env, napi_callback_info info)
{
         // 数据个数
         int num = 10;
         // 创建output_buffer
         napi_value output_buffer;
         void *output_ptr = NULL;
         napi_create_arraybuffer(env, num * sizeof(int32_t), &output_ptr, &output_buffer);
 
         // output_array
         napi_value output_array;
         napi_create_typedarray(env, napi_int32_array, num, output_buffer, 0, &output_array);
 
         // 给output_ptr、output_buffer赋值
         int32_t * output_bytes = (int32_t *)output_ptr;
         for (int32_t i = 0; i < num; i++) {
             output_bytes[i] = i;
         }
 
         for (int32_t i = 0; i < num; i++) {
             OH_LOG_INFO(LOG_APP, "NAPI2TS: C++  %{public}d", *((int32_t *)(output_ptr) + i));
         }
 
         return output_array;
}

实现效果

image.png

image.png

场景三:class对象传给native

调用接口:

napi_get_named_property

napi_call_function

实现能力:

实现了 ArkTS 侧向 Native C++ 侧传递 class 对象。

主要通过 napi_get_named_property 获取 class 里面的参数、方法,然后通过 napi_call_function 调用里面的方法。

核心代码解释

Index.ets

class test {
  name:string = 'zhangsan';
  age:number = 18
​
  add(a:number, b:number): number{
    return a + b;
  }
  sub(a:number, b:number): number{
    return a - b;
  }
}
let A:test = new test();
testNapi.testObject(A);

Native侧获取 ArkTS侧传过来的对象。

static napi_value TestObject(napi_env env, napi_callback_info info) {
    size_t requireArgc = 1;
    size_t argc = 1;
    napi_value args[1] = {nullptr};
​
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    // 获取参数对象的类型
    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);
    
    // 通过napi_get_named_property函数获取参数对象中名为"name"和"age"的属性值,并存储在name和age变量中。
    napi_value name, age;
    napi_get_named_property(env, args[0], "name", &name);
    napi_get_named_property(env, args[0], "age", &age);
​
    // 获取name属性值的字符串长度,并根据其长度动态分配内存,然后获取字符串内容,并打印出来。
    size_t itemLength;
    napi_get_value_string_utf8(env, name, nullptr, 0, &itemLength);
    //    std::string str(itemLength, '\0');
    char *str = new char[itemLength + 1];
    napi_get_value_string_utf8(env, name, &str[0], itemLength + 1, &itemLength);
    OH_LOG_INFO(LOG_APP, "name is %{public}s", str);
    // 获取age属性值的无符号整数,并打印出来。
    uint32_t length;
    napi_get_value_uint32(env, age, &length);
    OH_LOG_INFO(LOG_APP, "age is %{public}d", length);
​
    //获取参数对象中名为"add"和"sub"的方法,并存储在add和sub变量中。
    napi_value add, sub;
    napi_get_named_property(env, args[0], "add", &add);
    napi_get_named_property(env, args[0], "sub", &sub);
​
    // 创建参数数组
    napi_value arr[2];
    napi_create_int32(env, 10, &arr[0]);
    napi_create_int32(env, 5, &arr[1]);
    // 调用参数对象的"add"和"sub"方法,传递这个数组作为参数,并将结果存储在result1和result2变量中。
    napi_value result1, result2;
    napi_call_function(env, args[0], add, 2, arr, &result1);
    napi_call_function(env, args[0], sub, 2, arr, &result2);
    // 获取result1和result2变量中的无符号整数值,并打印出来
    uint32_t res1, res2;
    napi_get_value_uint32(env, result1, &res1);
    napi_get_value_uint32(env, result2, &res2);
    OH_LOG_INFO(LOG_APP, "res1 is %{public}d", res1);
    OH_LOG_INFO(LOG_APP, "res2 is %{public}d", res2);
​
    return nullptr;
}

实现效果

image.png

场景四:HashMap转换成JSON给native

调用接口:

napi_get_value_string_utf8

nlohmann::json::parse

实现能力:

实现了 HashMap 转换成JSON,然后传给Native C++ 侧,C++没有直接反序列化的接口,需要使用三方库,本demo采用lycium交叉编译工具编译json三方库。

先将储存HashMap的args[0]转换成字符串,然后通过 nlohmann::json::parse 解析 JSON 字符串 jsonStr,并将解析后的结果存储在 myMap 变量中,最后获取 myMap 中的数据。

核心代码解释

ArkTS侧转Json JSON.stringify不支持对HashMap操作,需要先将其转成Record。

map2rec(map:HashMap<string, ESObject>):Record<string,ESObject>{
  // map转Record
  let Rec:Record<string,ESObject> = {}
  map.forEach((value:ESObject, key:string) => {
    if(value instanceof HashMap){//value可能为HashMap
      let vRec:Record<string,ESObject> = this.map2rec(value)
      value = vRec
    }
    Rec[key] = value
  })
  return Rec
}

然后使用JSON.stringify序列化。

let map: HashMap<string, string> = new HashMap<string, string>()
for(let i=0;i<10;i++){
  let key:string = i.toString()
  let val:string = "test:" + i.toString()
  map.set(key,val)
}
this.myMap.set("map",map)
let arr: Array<string> = new Array<string>(10)
for(let i=0;i<10;i++){
  let val:string = "test:" + i.toString()
  arr[i] = val
}
this.myMap.set("arr",arr)
let myRec:Record<string,ESObject> = this.map2rec(this.myMap)
let str:string = JSON.stringify(myRec)
testNapi.map_json(str)

native侧 C++没有直接反序列化的接口,需要使用三方库。 本demo采用lycium交叉编译工具编译json三方库。

image.png

//将value转换成字符串
static std::string value2String(napi_env env, napi_value value) {
    size_t stringSize = 0;
    napi_get_value_string_utf8(env, value, nullptr, 0, &stringSize); // 获取字符串长度
    std::string valueString;
    valueString.resize(stringSize + 1);
    napi_get_value_string_utf8(env, value, &valueString[0], stringSize + 1, &stringSize); // 根据长度传换成字符串
    return valueString;
}
static napi_value map_json(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);
    // 将储存HashMap的args[0]转换成字符串
    std::string jsonStr = value2String(env, args[0]);
    // 解析 JSON 字符串 jsonStr,并将解析后的结果存储在 myMap 变量中
    auto myMap = nlohmann::json::parse(jsonStr.c_str());
    // 从 myMap 对象中获取名为 "arr" 的数组,并取其第三个元素(索引为2),并将结果存储在 val 变量中。
    std::string val = myMap.at("arr").at(2);
    OH_LOG_INFO(LOG_APP, "%{public}s", val.c_str());
​
    return nullptr;
}

场景五:pixelmap类型的传递

调用接口:

napi_get_value_string_utf8

heif_context_read_from_file

实现能力:

实现了将 pixelmap 类型的数据通过传入文件在沙箱中的路径来实现 Native C++ 与 ArkTS 之间的传递,这里例举了 HEIC 格式的图片进行传递,其他格式可以先通过传入的文件路径获取 pixelmap,然后使用 OH_PixelMap_InitNativePixelMap 初始化PixelMap对象数据,接着使用 OH_PixelMap_GetImageInfo 获取图片信息,最对图片继续处理。

首先在 ArkTS 侧将文件路径以字符串的方式传给 Native C++ 侧,Native C++ 侧获取传入的文件路径,通过 heif_context_read_from_file 从应用沙箱中读取 HEIC 图片,然后通过 heif_context_read_from_memory 从内存中读取 HEIC 图像,通过 heif_context_get_primary_image_handle 获取主图像句柄,通过 heif_decode_image 从句柄中解码图像,通过 heif_image_get_width、heif_image_get_height 和 heif_image_get_plane_readonly 获取获取图像尺寸和数据,接着对图像进行处理,最后清理资源,返回pixelmap。

核心代码解释

Index.ets,通过传文件路径给Native侧。

@State pixmaps: image.PixelMap[] = [];
private fileDir: string = getContext(this).getApplicationContext().filesDir;
// 解码heif图片
let filepath = this.fileDir + `/test${i}.heic`;
console.log('testTag input filename: ' + filepath);
this.pixmaps.push(testNapi.decodeHeifImage(filepath));

获取ArkTS侧传来的文件路径,处理完后返回pixelmap给ArkTS侧。

// 定义转换函数
void swapRBChannels(uint8_t *pixels, int pixelCount) {
    for (int i = 0; i < pixelCount; i++) {
        std::swap(pixels[i * 4], pixels[i * 4 + 2]); // 交换像素数据中的红色和蓝色通道
    }
}
​
static napi_value DecodeHeifImage(napi_env env, napi_callback_info info) {
    napi_value pixel_map = nullptr;
​
    OH_LOG_INFO(LOG_APP, "Beginning DecodeHeifImage!");
​
    // 解析参数JS -> C++
    size_t argc = 1;
    napi_value argv[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
​
    size_t filenameSize;
    char filenameBuffer[512];
    napi_get_value_string_utf8(env, argv[0], filenameBuffer, sizeof(filenameBuffer), &filenameSize);
    std::string input_filename(filenameBuffer, filenameSize); // 获取输入文件名
    OH_LOG_INFO(LOG_APP, "filename:  %{public}s", input_filename.c_str()); // 记录文件名
​
    // 创建 heif_context
    heif_context *ctx = heif_context_alloc();
    // 从应用沙箱中读取 HEIC 图片
    heif_error err = heif_context_read_from_file(ctx, input_filename.c_str(), nullptr);
​
    // 从内存中读取 HEIC 图像
    // heif_error err = heif_context_read_from_memory(ctx, image_data.get(), static_cast<size_t>(imageDataSize),
    //     nullptr);
​
    if (err.code != heif_error_Ok) { // 检查是否读取成功
        OH_LOG_ERROR(LOG_APP, "读取 heif 图片错误:  %{public}s", err.message);
        heif_context_free(ctx); // 释放资源
        return nullptr;
    }
​
    OH_LOG_INFO(LOG_APP, "读取 heif 图片正常!");
​
    // 获取主图像句柄
    heif_image_handle *handle;
    err = heif_context_get_primary_image_handle(ctx, &handle);
​
    if (err.code != heif_error_Ok) { // 检查获取句柄是否成功
        OH_LOG_ERROR(LOG_APP, "获取主图像句柄错误: %{public}s", err.message);
        heif_image_handle_release(handle);
        heif_context_free(ctx); // 释放资源
        return nullptr;
    }
​
    OH_LOG_INFO(LOG_APP, "获取主图像句柄正常!");
​
    // 从句柄中解码图像
    heif_image *heif_img;
    err = heif_decode_image(handle, &heif_img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, nullptr);
​
    if (err.code != heif_error_Ok) { // 检查解码是否成功
        OH_LOG_ERROR(LOG_APP, "从句柄中解码图像错误: %{public}s", err.message);
        heif_image_handle_release(handle);
        heif_context_free(ctx); // 释放资源
        return nullptr;
    }
​
    OH_LOG_INFO(LOG_APP, "从句柄中解码图像正常!");
​
    // 获取图像尺寸
    int width, height;
    width = heif_image_get_width(heif_img, heif_channel_interleaved);
    height = heif_image_get_height(heif_img, heif_channel_interleaved);
​
    OH_LOG_INFO(LOG_APP, "heif图片width: %{public}d", width);
    OH_LOG_INFO(LOG_APP, "heif图片height: %{public}d", height); 
​
    // 获取图像数据
    int stride;
    uint8_t *data = heif_image_get_plane_readonly(heif_img, heif_channel_interleaved, &stride);
    if (data == nullptr) {
        OH_LOG_ERROR(LOG_APP, "读取到的图像数据为空"); 
        return nullptr;
    }
​
    const size_t pixel_count = width * height; // 像素总数
    const size_t row_bytes = width * 4;        // 每一行的字节数,每个像素4个字节
    const size_t total_size = pixel_count * 4; // 计算平面的总数据大小
    OH_LOG_INFO(LOG_APP, "图片平面的总数据大小: %{public}d", total_size); 
​
    uint8_t *new_data = data;                 // 默认指向原数据
    bool needAlignment = stride != row_bytes; // 是否需要字节对齐
    if (needAlignment) { // 如果需要字节对齐
        new_data = new uint8_t[total_size]; // 分配新的内存空间
        // 字节对齐
        for (int row = 0; row < height; row++) {
            memcpy(new_data + row * row_bytes, data + row * stride, row_bytes); // 将数据按行复制到新的内存空间中
        }
    }
​
    // OH_PixelMap_CreatePixelMap目前颜色编码格式只支持BGRA,需要转换颜色格式(RGBA to BRGA)
    swapRBChannels(new_data, pixel_count); // 调用像素通道交换函数
​
    struct OhosPixelMapCreateOps createOps;
    createOps.width = width;
    createOps.height = height;
    createOps.pixelFormat = 4; // 目前颜色编码格式只支持BGRA
    createOps.alphaType = 0;
​
    int32_t res = OH_PixelMap_CreatePixelMap(env, createOps, (void *)new_data, total_size, &pixel_map);
    if (res != IMAGE_RESULT_SUCCESS || pixel_map == nullptr) { // 检查创建pixelMap是否成功
        OH_LOG_ERROR(LOG_APP, "创建pixelMap错误"); 
        return nullptr;
    }
    OH_LOG_INFO(LOG_APP, "创建pixelMap成功"); 
​
    // 清理资源
    if (needAlignment) {
        delete[] new_data; // 释放新数据内存
    }
    heif_image_release(heif_img); // 释放图像资源
    heif_image_handle_release(handle); // 释放图像句柄
    heif_context_free(ctx); // 释放上下文
​
    return pixel_map; 
}
  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青少年编程作品集

你的赞赏将带来极佳的运气

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值