【鸿蒙实战开发】跨语言的复杂参数类型传递

100 篇文章 2 订阅
100 篇文章 1 订阅

场景说明:

我们经常在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;
}

实现效果

场景二: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;
}

实现效果

场景三: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;
}

实现效果

场景四: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三方库。

//将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; 
}

鸿蒙全栈开发全新学习指南

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以要有一份实用的鸿蒙(HarmonyOS NEXT)学习路线与学习文档用来跟着学习是非常有必要的。

针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

本路线共分为四个阶段

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

在这里插入图片描述

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

在这里插入图片描述

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值