事情是从一个c的函数库开始的...
这个c的库能够导出 iOS的.a 和 android的 .so 文件,然后通过网上资料的查找,发现了ffi可以处理这种dart直接调用c的方法.
当然如果资源充足,也可以使用channel方式,channel需要各端原生写一套接口,iOS还是很方便的,但是android需要JNI来桥接,相当于是两套接口...
下面就来记录下,从开始接触ffi,到遇到的坑,再到后来完成需求的过程
1.ffi的基本使用
ffi的基本使用方式 这里边可以让你尝试一下,怎么从最简单 a+b 入手,以及c的数据类型在dart与ffi中的对应关系(这一点很重要,类型对应不上,其他都白扯)
2.dart中结构体的定义
虽然上面文章中介绍了关于Struct的相关内容,但是在实际使用中还是遇到了问题
- c 结构体中char[],如何在dart中对应
- dart中char[]如何赋值
typedef struct
{
uint8_t name[128];
uint32_t age;
}person_t;
比如这个结构体,有一个数组成员,128个字节,如何在dart中定义这个结构体,写个List试试?显然是不行的,但是让我发现了一句提示
Try using 'int', 'double', 'Array', 'Pointer', or subtype of 'Struct' or 'Union'.
这个提示告诉我们dart中的结构体只能通过'int', 'double', 'Array', 'Pointer', or subtype of 'Struct' or 'Union'这几个类型来定义,Array进入了我的视野,点击进去便豁然开朗
/// A fixed-sized array of [T]s.
class Array<T extends NativeType> extends NativeType {
/// Const constructor to specify [Array] dimensions in [Struct]s.
///
/// ```dart
/// class MyStruct extends Struct {
/// @Array(8)
/// external Array<Uint8> inlineArray;
///
/// @Array(2, 2, 2)
/// external Array<Array<Array<Uint8>>> threeDimensionalInlineArray;
/// }
/// ```
///
/// Do not invoke in normal code.
const factory Array(int dimension1,
[int dimension2,
int dimension3,
int dimension4,
int dimension5]) = _ArraySize<T>;
/// Const constructor to specify [Array] dimensions in [Struct]s.
///
/// ```dart
/// class MyStruct extends Struct {
/// @Array.multi([2, 2, 2])
/// external Array<Array<Array<Uint8>>> threeDimensionalInlineArray;
///
/// @Array.multi([2, 2, 2, 2, 2, 2, 2, 2])
/// external Array<Array<Array<Array<Array<Array<Array<Array<Uint8>>>>>>>> eightDimensionalInlineArray;
/// }
/// ```
///
/// Do not invoke in normal code.
const factory Array.multi(List<int> dimensions) = _ArraySize<T>.multi;
}
正是我们想要的
所以上面的问题1解决了,最后的dart结构体为
class Person extends Struct {
@Array(128)
external Array<Uint8> name;
@Uint32()
external int age;
}
然后就是如何给这个结构体赋值了,array怎么赋值?半天想不明白怎么弄,然后就是各种尝试,发现ffi这个框架提供了一些方法
// 创建一个以零结尾的[Utf8]代码单元数组。
toNativeUtf8
然后想到了一个办法我既然通过dartString获取到了一个cString比如就是(123\0),那我可以手动去给这个array赋值啊,取出Pointer<Utf8>第一个字节,放到数组的第一个字节,以此类推,然后就开始搞代码
test() async {
Pointer<Person> pperson = calloc.call();
Pointer<Utf8> pUtf8 = "123".toNativeUtf8();
Pointer<Uint8> head = pUtf8.cast<Uint8>();
var i = 0;
while (head[i] != 0) {
print(head[i]);
pperson.ref.name[i] = head[i];
i++;
}
print("----------");
print(pperson.ref.name[0]);
print(pperson.ref.name[1]);
print(pperson.ref.name[2]);
}
flutter: 49
flutter: 50
flutter: 51
flutter: ----------
flutter: 49
flutter: 50
flutter: 51
结果确实是我们想要的,然后稍微封装一下
// dart String 转 c char[] 数组 这里需要传入初始化过的空数组
void cArrayFromDartString(String string, Array<CChar> cArray) {
Pointer<Utf8> pUtf8 = string.toNativeUtf8();
Pointer<Uint8> head = pUtf8.cast<Uint8>();
var i = 0;
while (head[i] != 0) {
cArray[i] = head[i];
i++;
}
}
//调用方式
cArrayFromDartString("123", pperson.ref.name);
最后呢还有一步是结构体的嵌套,比如这个人有个狗狗
class Dog extends Struct {
@Array(128)
external Array<Uint8> name;
}
我们可以定义person如下
class Person extends Struct {
@Array(128)
external Array<Uint8> name;
@Uint32()
external int age;
external Dog dog;
}
然后测试一下
test() async {
Pointer<Person> pperson = calloc.call();
cArrayFromDartString("123", pperson.ref.name);
print("人名--------");
print(pperson.ref.name[0]);
print(pperson.ref.name[1]);
print(pperson.ref.name[2]);
Pointer<Dog> pDog = calloc.call();
cArrayFromDartString("456", pDog.ref.name);
pperson.ref.dog = pDog.ref;
calloc.free(pDog);
print("狗狗名--------");
print(pperson.ref.dog.name[0]);
print(pperson.ref.dog.name[1]);
print(pperson.ref.dog.name[2]);
}
完美!!!
再变态一点,有好几条狗
class Person extends Struct {
@Array(128)
external Array<Uint8> name;
@Uint32()
external int age;
@Array(5)
external Array<Dog> dogs;
}
....
至此,关于结构体的创建和赋值就完成了
3. 当c函数是个耗时函数,在dart中怎么做到异步呢
这个问题是在时间的过程中发现的,因为c函数耗时,导致UI卡住的问题,你可以理解这一点为sleep,即使使用Future也无济于事,例如
test() {
test1();
}
Future test1() async {
sleep(Duration(seconds: 10));
}
调用test依旧会卡,所以选择了isolate解决,关于isolate就不过多介绍了
但是最后通过比较发现,使用channel的速度竟然比ffi还要快,这一点是我没想到的,纳尼?
时间竟然能差出一倍还多,当然我感觉还是我的方法使用不当造成的