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

实现效果

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

HarmonyOS码上奇行
8.5k 声望3.1k 粉丝

欢迎关注 HarmonyOS 开发者社区:[链接]