07、Flutter FFI 数组

 
Flutter FFI 学习笔记系列

  1. 《Flutter FFI 最简示例》
  2. 《Flutter FFI 基础数据类型》
  3. 《Flutter FFI 函数》
  4. 《Flutter FFI 字符串》
  5. 《Flutter FFI 结构体》
  6. 《Flutter FFI 类》
  7. 《Flutter FFI 数组》
  8. 《Flutter FFI 内存管理》
  9. 《Flutter FFI Dart Native API》
      
     

在前面的章节中,介绍了基础数据类型、字符串、结构体等,接下来介绍一下 FFI 里面的数组。
  
 

1、数组的表示方式

  C语言中的数组可以用 int[] 表示,也可以用 int* 表示。在 Dart 中一般采用 Pointer<T extends NativeType> 表示 C 语言中的数组。
  如果在 C 语言中采用指针表示数组,一般需要明确数组的长度。例如:

typedef struct {
    
    
    int32_t *data;
    int32_t length;
} IntArray;

IntArray *createIntArray() {
    
    
    auto *result = (IntArray *) malloc(sizeof(IntArray));
    result->length = 5;
    result->data = (int32_t *) malloc(result->length * sizeof(int32_t));
    for (int i = 0; i < result->length; i++) {
    
    
        result->data[i] = i * 2;
    }
    return result;
}

  代码说明:

  • 上述代码中,通过定义 IntArray 结构体来表示一个整型数组,data 表示数据,length 表示长度;
     

  Dart 中,采用 Pointer<T extends NativeType> 表示数组,例如:int32_t 数组用Pointer<Int32> 表示。
  dart:ffi 中定义了一些扩展函数,有助于我们将 Pointer<T extends NativeType> 当成数组使用,例如 Pointer<Int32>

extension Int32Pointer on Pointer<Int32> {
    
    
    //通过下标读取数组元素
    external int operator [](int index);
    
    //通过下标改写数组元素
    external void operator []=(int index, int value);
    
    //将指针数组转为List,可以像List一样使用各种便捷函数,例如:forEach
    //需要注意的是,使用时不能超出数组范围
    external Int32List asTypedList(int length);
}

  说明:

  • Int32Pointer 扩展函数中,定义了[][]=操作符函数,用于读取或改写数组指定索引的值;
  • asTypedList()函数可以把数组转为 Dart List,以便可以使用各种 List 的便捷函数。
     

2、数组用法示例

  下面的示例演示了数组的基本用法。
  首先,在 C 中定义数组结构体、创建数组的方法、访问数组的方法:

#include <malloc.h>
#include <cstring>

#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))

//定义IntArray结构体
typedef struct {
    
    
    int32_t *data;
    int32_t length;
} IntArray;

//创建数组
DART_API IntArray *createIntArray() {
    
    
    auto *result = (IntArray *) malloc(sizeof(IntArray));
    result->length = 5;
    result->data = (int32_t *) malloc(result->length * sizeof(int32_t));
    for (int i = 0; i < result->length; i++) {
    
    
        result->data[i] = i * 2;
    }
    return result;
}

//获取数组最大值
DART_API int32_t getMaxInt(const int32_t *intArray, int32_t length) {
    
    
    int32_t max = intArray[0];
    for (int i = 1; i < length; i++) {
    
    
        if (intArray[i] > max) {
    
    
            max = intArray[i];
        }
    }
    return max;
}

//定义结构体Point,表示平面上的一点
typedef struct {
    
    
    int32_t x;
    int32_t y;
} Point;

//获取若干几点
DART_API Point *getPoints(int32_t length) {
    
    
    auto *points = (Point *) malloc(length * sizeof(Point));
    for (int i = 0; i < length; ++i) {
    
    
        points[i].x = 100 * (i + 1) + i;
        points[i].y = 200 * (i + 1) + (i + 1);
    }
    return points;
}

  然后,在 Dart 中定义相对应的结构体、函数类型:

//对应C中的IntArray结构体
class IntArray extends Struct {
    
    
  external Pointer<Int32> data;

  @Int32()
  external int length;
}

//对应C中的Point结构体
class Point extends Struct {
    
    
  @Int32()
  external int x;

  @Int32()
  external int y;

  String toDebugString() => "{x=$x, y=$y}";
}

typedef Native_createIntArray = Pointer<IntArray> Function();
typedef FFI_createIntArray = Pointer<IntArray> Function();

typedef Native_getMaxInt = Int32 Function(Pointer<Int32>, Int32);
typedef FFI_getMaxInt = int Function(Pointer<Int32>, int);

typedef Native_getPoints = Pointer<Point> Function(Int32 length);
typedef FFI_getPoints = Pointer<Point> Function(int length);

  最后,在 Dart 中编写调用数组的接口:

//加载符号
DynamicLibrary nativeApi = Platform.isAndroid
    ? DynamicLibrary.open("libnative_ffi.so")
    : DynamicLibrary.process();

//查找函数符号 - createIntArray
FFI_createIntArray createIntArrayFunc =
    nativeApi.lookupFunction<Native_createIntArray, FFI_createIntArray>(
        "createIntArray");

//查找函数符号 - getMaxInt
FFI_getMaxInt getMaxIntFunc =
    nativeApi.lookupFunction<Native_getMaxInt, FFI_getMaxInt>("getMaxInt");

//查找函数符号 - getPoints
FFI_getPoints getPointsFunc =
    nativeApi.lookupFunction<Native_getPoints, FFI_getPoints>("getPoints");

//创建IntArray
Pointer<IntArray> pIntArray = createIntArrayFunc();
IntArray intArray = pIntArray.ref;

//打印数组元素
for (int i = 0; i < intArray.length; i++) {
    
    
  print("intArray[$i]=${intArray.data[i]}");
}

//获取数组中的最大值
int max = getMaxIntFunc(intArray.data, intArray.length);
print("max of intArray=$max");

//将Int数组转为List使用
Int32List int32list = intArray.data.asTypedList(intArray.length);
int32list.forEach((it) => print("int32list=$it"));

//创建结构体数组
int length = 3;
Pointer<Point> pPointArray = getPointsFunc(length);
for (int i = 0; i < length; i++) {
    
    
  print("points[$i]=${pPointArray[i].toDebugString()}");
}

//释放内存
calloc.free(intArray.data);
calloc.free(pIntArray);
calloc.free(pPointArray);

  上述代码输出结果如下:

I/flutter ( 9388): intArray[0]=0
I/flutter ( 9388): intArray[1]=2
I/flutter ( 9388): intArray[2]=4
I/flutter ( 9388): intArray[3]=6
I/flutter ( 9388): intArray[4]=8
I/flutter ( 9388): max of intArray=8
I/flutter ( 9388): int32list=0
I/flutter ( 9388): int32list=2
I/flutter ( 9388): int32list=4
I/flutter ( 9388): int32list=6
I/flutter ( 9388): int32list=8
I/flutter ( 9388): points[0]={x=100, y=201}
I/flutter ( 9388): points[1]={x=201, y=402}
I/flutter ( 9388): points[2]={x=302, y=603}

3、其它类型的数组

  
  上面演示了 Int32 数组和 Struct数组,其它类型的数组,例如:DoubleFloatInt8……其实用法都差不多一样。
 

4、零长动态数组的处理

  
   C语言零长动态数组与 Dart 的交互,直接给出代码:
  
  C代码:

#include "dart_api/dart_api.h"
#include "dart_api/dart_native_api.h"
#include "dart_api/dart_api_dl.h"

typedef struct {
    
    
    int len;
    int buffer[0];
} DynamicArray;

// 该函数用于创建一个 DynamicArray,给 Dart 使用。
DART_EXPORT DynamicArray *createArray(int len) {
    
    
    auto arr = (DynamicArray *) calloc(sizeof(DynamicArray) + sizeof(int) * len, 1);
    arr->len = len;
    return arr;
}

  Dart代码:


void main() {
    
     runApp(Demo()); }

class Demo extends StatefulWidget {
    
    
  const Demo({
    
    Key? key}) : super(key: key);

  @override
  _DemoState createState() => _DemoState();
}

const int intMaxValue = 9223372036854775807;

class DynamicArray extends Struct {
    
    
  @Int32()
  external int len;

  ///给 dimension1 参数赋一个很大的值,绕过 ffi-patch.dart 的 _checkIndex() 的检查
  @Array(intMaxValue)
  external Array<Int32> data;
}

typedef Native_Dart_CreatArray = Pointer<DynamicArray> Function(Int32);
typedef FFI_Dart_CreatArray = Pointer<DynamicArray> Function(int);

class _DemoState extends State<Demo> {
    
    
  late DynamicLibrary nativeApi;

  @override
  void initState() {
    
    
    super.initState();
    testNative();
  }

  @override
  Widget build(BuildContext context) {
    
    
    return MaterialApp(home: Scaffold(body: Center(child: Text("FFI Demo"))));
  }

  void testNative() {
    
    
    nativeApi = Platform.isAndroid
        ? DynamicLibrary.open("libnative_ffi.so")
        : DynamicLibrary.process();

    FFI_Dart_CreatArray createArray =
        nativeApi.lookupFunction<Native_Dart_CreatArray, FFI_Dart_CreatArray>(
            "createArray");

    Pointer<DynamicArray> array = createArray(10);
    print("array len=${array.ref.len}");

    for (int i = 0; i < array.ref.len; i++) {
    
    
      array.ref.data[i] = i * 2;
    }

    for (int i = 0; i < array.ref.len; i++) {
    
    
      final int a = array.ref.data[i];
      print("i=$a");
    }
  }
}

说明

  • 在 dart 中定义 Array 的时候,必须要指定数组长度,而且必须为正数,如果是 0 则报错,编译不过;
  • 问题在于如果解决数据长度是动态变化的问题。这里的解决方案是给 dimension1 设置一个很大的值,这样可以绕过绕过 ffi-patch.dart_checkIndex() 的检查。
  • 但是这样一来,就不能在 Dart 中创建数组了,需要 C 提供一个创建数组的函数 —— createArray()

  程序运行结果:

I/flutter (25532): array len=10
I/flutter (25532): i=0
I/flutter (25532): i=2
I/flutter (25532): i=4
I/flutter (25532): i=6
I/flutter (25532): i=8
I/flutter (25532): i=10
I/flutter (25532): i=12
I/flutter (25532): i=14
I/flutter (25532): i=16
I/flutter (25532): i=18

  
 

5、总结

  上面介绍了数组在 Dart 与 C 中的相互调用。需要说明一点:其实还有另一种创建数组的方法,就是使用malloccalloc。在接下来的章节中,将会介绍FFI内存管理,同时也会介绍 Dart 如何通过 malloccalloc 创建数组,欢迎关注。

  
 

猜你喜欢

转载自blog.csdn.net/eieihihi/article/details/119599375