Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《Flutter FFI 数组》
- 《Flutter FFI 内存管理》
- 《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()
函数可以把数组转为 DartList
,以便可以使用各种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
数组,其它类型的数组,例如:Double
、Float
、Int8
……其实用法都差不多一样。
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 中的相互调用。需要说明一点:其实还有另一种创建数组的方法,就是使用malloc
和 calloc
。在接下来的章节中,将会介绍FFI内存管理,同时也会介绍 Dart 如何通过 malloc
和 calloc
创建数组,欢迎关注。