场景说明:
我们经常在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;
}