鸿蒙开发实践——跨语言的复杂参数类型传递

场景说明:

我们经常在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;
}
实现效果

DD一下:在探索鸿蒙系统的学习过程中,不少人都曾遇到过各种棘手问题。小编结合自身学习与实践经历,将容易踩坑的环节和对应的解决思路整理成攻略,希望这些经验分享能为同样在学习鸿蒙的伙伴们提供一些参考和帮助:

请移步前往小编鸿蒙全栈开发学习指南 ​

1.鸿蒙全栈开发学习路线
2.OpenHarmony开发基础
3.OpenHarmony北向开发环境搭建
4.鸿蒙南向开发环境的搭建
5.鸿蒙生态应用开发白皮书V2.0 & V3.0
6.鸿蒙开发面试真题(含参考答案) 
7.TypeScript入门学习手册
8.OpenHarmony 经典面试题(含参考答案)
9.OpenHarmony设备开发入门【最新版】
10.沉浸式剖析OpenHarmony源代码
11.系统定制指南
12.【OpenHarmony】Uboot 驱动加载流程
13.OpenHarmony构建系统--GN与子系统、部件、模块详解
14.ohos开机init启动流程
15.鸿蒙版性能优化指南
......

场景二: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的args0转换成字符串,然后通过 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; 
}
### 鸿蒙开发中实现汇率转换功能 在鸿蒙开发环境中,为了实现在应用内的汇率转换功能,开发者可以借鉴已有的技术方案并结合具体需求进行定制。 #### 多语言与多币种的支持机制 支持多种语言(如中文、英文等),以及不同货币单位的处理是通过系统架构层面的设计来达成的[^1]。这意味着,在设计应用程序之初就需要考虑如何让程序能够识别用户的偏好设置,并据此提供相应的服务。对于货币而言,则涉及到依据预设或者实时获取到的汇率来进行金额之间的相互转换。 #### 汇率更新策略 值得注意的是,这里所使用的汇率并非实时变动的数据流,而是由平台方定期维护的一个相对固定的数值表。例如,华为会基于这一套内部设定好的比率去刷新其平台上显示给消费者的最终售价;但是这种变化并不会直接影响第三方卖家自行定价的商品成本结构——即商家仍然可以根据市场情况自主决定是否调整自家产品的标价[^2]。 #### 国际化与本地化的实践要点 从更广泛的角度来看待这个问题,“国际化”指的是创建一个灵活的基础框架以便于后续根据不同地区的需求做出相应改变;而“本地化”的过程则更加注重细节上的打磨,比如针对某一种特定的语言环境做精准的内容适配工作,这其中就包含了对当地常用支付手段及其背后逻辑的理解和支持[^3]。因此,在涉及跨国交易场景下的资金流转时,确保软件具备良好的跨文化交流能力显得尤为重要。 ```python def convert_currency(amount, from_currency, to_currency): exchange_rate = get_exchange_rate(from_currency, to_currency) # 获取汇率函数 converted_amount = amount * exchange_rate return round(converted_amount, 2) # 假定有一个方法用于查询最新的汇率数据 def get_exchange_rate(source, target): rates = { ('CNY', 'USD'): 0.15, ('USD', 'EUR'): 0.92, # 更多汇率... } try: rate = rates[(source.upper(), target.upper())] except KeyError as e: raise ValueError(f"No conversion available between {source} and {target}") from e return rate ``` 上述代码片段展示了简单的货币兑换算法,实际项目中应当连接至可靠的API接口以获得最新最准确的信息作为输入参数传递给`get_exchange_rate()`函数调用[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值