Flutter中FFI学习
- Dart FFI编程
- 概述
- NativeType(类型映射)
- Window安装GCC
- Dart调用C的函数
- 数组
- 字符串
- 结构体
Dart FFI编程
概述
dart:ffi
库可以使用Dart语言调用本地C语言API
,并读取、写入、分配和删除本地内存。FFI是指外部函数接口(Foregin function interface)。其他类似功能的术语包括本地接口和语言绑定。
具体关于如何将C语言指针映射为Dart中的指针类型的操作, 可以查看Dart FFI API参考文档
关于Dart语言的FFI使用, 可总结以下6个步骤:
- 导入
ffi
包 - 为被调用的C函数创建Dart Native签名(FFI类型前面,Navtive Type)
- 为被调用的C函数创建Dart函数前面(与第2不对应)
- 加载动态库
- 查找C函数,并将C函数指针映射为Dart函数(即将第2、3步创建的签名映射起来)
- 调用函数
NativeType(类型映射)
NativeType是在Dart中表示C语言中的数据结构,它不可在Dart中实例化,只能由Native返回。
Dart FFI与C基础数据类型映射表如下:
Window安装GCC
window平台安装GCC
把C/C++文件编译成.dll
动态库
gcc -shared -o libexample.dll libexample.c
Dart调用C的函数
//C语言函数
int calu(char *expression) {
printf("开始执行方法============");
for (size_t i = 0; i < 5; i++)
{
/* code */
printf("传入的值是===========%c\n", expression[i]);
}
return 8;
}
验证代码:
import 'dart:ffi';
import 'package:ffi/ffi.dart' as ffi;
///
/// 被Dart调用的C语言原型
/// int calu(char *expression)
///
///
/// int -> int32 (在c语言中int 是占32位)
/// char * -> Pointer<ffi.Utf8> 在C语言中char * 代表一个指针, Char 实际上int8,但是dart:ffi中没有对应的,
/// 所以我们需要使用到第三方那个库中的API, 该库中char*对应是utf8
/// ffi类型签名(native签名)把c的函数映射成native函数
typedef CaluNavtive = Int32 Function(Pointer<ffi.Utf8>);
/// Dart函数签名
/// 首先dart函数签名,应该使用dart中的签名, 如果是基础数据类型,我们可以直接使用dart中的基础数据类型
/// 但是如果是对象类型, 比如下面函数中我们就不能使用String,如果这里写String,是无法和Native的类型进行转换的
/// 像Dart中特有的非基础数据类型, 那么只能使用Navtive类型, 所以这里直接使用Pointer<ffi.Utf8>
typedef Calc = int Function(Pointer<ffi.Utf8>);
void main(List<String> arguments) {
// 加载动态库
var dyn = DynamicLibrary.open('./bin/libtest.dll');
//查找C函数
/// external F lookupFunction<T extends Function, F extends Function>(
Calc calc = dyn.lookupFunction<CaluNavtive, Calc>('calu');
// 首先我们方法需要的是一个pointer<ffi.ut8>类型的参数, 那么我们需要把dart类型的字符串转成这个类型传递进去
final exp = "1234567".toNativeUtf8();
//调用函数
var result = calc(exp);
print("result======$result");
}
输出结果:
数组
Dart与C传递数组, 只能使用堆内存
, 也就是动态内存, 当我们需要再FFI中使用动态内存分配时,需要依赖一个官方开发的外部包ffi
,注意与Dart内部核心包ffi
进行区分
外部包package:ffi
主要提供了动态内存分配与字符串的处理,是FFI开发中必不可少的依赖,因此不要忘记在pubspec.yaml
中配置依赖
- C语言代码:
void test_array(int *arr, int len) {
for (size_t i = 0; i < len; i++)
{
/* code */
printf("%d\n", arr[i]);
}
}
- Dart代码:
/// C语言函数
/// void test_array(int *arr, int len)
/// ffi函数类型(Navtive)
typedef testArrayNavtive = Void Function(Pointer<Int32>, Int32);
/// Dart函数
typedef testArray = void Function(Pointer<Int32>, int);
/// Dart层创建一个整形数组, 传递给C语言, 然后C语言循环打印
void test_array(List list) {
// 需要动态分配一个内存
Pointer<Int32> pArr = ffi.malloc.allocate(sizeOf<Int32>() * list.length);
//循环list把内容都加入到数组中
for (var i = 0; i < list.length; i++) {
// elementAt是做一个便宜指针操作
pArr.elementAt(i).value = list[i];
}
// 加载动态库
var dyn = DynamicLibrary.open('./bin/libtest.dll');
//查找C函数
/// external F lookupFunction<T extends Function, F extends Function>(
testArray testarrayTemp =
dyn.lookupFunction<testArrayNavtive, testArray>('test_array');
testarrayTemp(pArr, list.length);
// 释放数组
ffi.malloc.free(pArr);
}
- 调用:
void main(List<String> arguments) {
// 调用打印数组
List list = [1, 2, 67, 8, 23, 88, 90, 32];
test_array(list);
}
- 结果:
字符串
字符串的本质就是字节数组, 因此传递字符串就是传递数组。
- C语言函数:
// 测试字符串的传递
void test_string(char *str, int len) {
printf("便利dart传递进来的字符串======%s", str);
for (size_t i = 0; i < len; i++)
{
/* code */
printf("%d\n", str[i]);
}
}
- Dart函数
/// ffi函数
typedef testStringNavtive = Void Function(Pointer<Int8>, Int32);
/// Dart函数
typedef testString = void Function(Pointer<Int8>, int);
void test_string(String message) {
// 如果传递的字符串是常见, 就不用动态分配内存, 但是如果是一个变量,那么我们需要动态分配内存
Pointer<Int8> charPointer =
ffi.malloc.allocate(sizeOf<Int8>() * message.length);
charPointer = message.toNativeUtf8().cast<Int8>();
// 加载动态库
var dyn = DynamicLibrary.open('./bin/libtest.dll');
//查找C函数
/// external F lookupFunction<T extends Function, F extends Function>(
testString tesstringTemp =
dyn.lookupFunction<testStringNavtive, testString>('test_string');
tesstringTemp(charPointer, message.length);
ffi.malloc.free(charPointer);
}
- 结果:
上述Dart中传递一个字符串给C语言(char *), 我们使用了Pointer来传递的, 但是我看了很多其他的资料, 都是Dart中的Pointer<ffi.utf8>就对饮C语言的char *
,但是当我使用时,报如下错误:
不知道如何解决,后续待研究
结构体
目前,FFI中,结构体不能直接 作为函数传递,但可以作为返回值。
- C语言中代码:
#include <stdio.h>
#include <stdlib.h>
// 定义一个结构体
typedef struct struct_ts
{
/* data */
char * name;
int age;
} Person;
// 创建一个Person的对象
Person create_person(char *name, int age) {
Person p = {};
p.name = name;
p.age = age;
return p;
}
// 创建一个Person的指针对象
Person * get_person(char *name, int age) {
Person *p = (Person *)malloc(sizeof(Person));
p->name = name;
p->age = age;
return p;
}
- Dart中创建和C语言中对应的结构体代码:
class Person extends Struct {
external Pointer<ffi.Utf8> name;
@Int32()
external int age;
}
- Dart中的结构体只能用于和C语言中结构体映射,FFI 会自动生成
setter/gette
r 方法,用于中访问内存中Native
结构体的字段值; - 如果字段的数据类型不是
NativeType
,则需要使用NativeType
进行修饰(如:int, double
);否则则不需要(如:Pointer
类型则不需要); Struct
中的所有字段都必须使用external
关键词进行修饰;
注意:不能实例化该 Dart 类,仅用于指向 Native 内存(即结构体是由C分配的,Dart只是持有一个引用而已),如果要实例化该类,则应该由 C 语言提供对应的创建/销毁方法,由Dart调用。
- Dart调用代码:
/// Person create_person(char *name, int age)
/// Person * get_person(char *name, int age)
typedef createPersonNavtive = Person Function(Pointer<ffi.Utf8>, Int32);
typedef createPerson = Person Function(Pointer<ffi.Utf8>, int);
typedef getPersonNavtive = Pointer<Person> Function(Pointer<ffi.Utf8>, Int32);
typedef getPerson = Pointer<Person> Function(Pointer<ffi.Utf8>, int);
void test_struct() {
// 加载动态库
var dyn = DynamicLibrary.open('./bin/libstruct_ts.dll');
//查找C函数
/// external F lookupFunction<T extends Function, F extends Function>(
final create_person =
dyn.lookupFunction<createPersonNavtive, createPerson>('create_person');
var get_person =
dyn.lookupFunction<getPersonNavtive, getPerson>('get_person');
// 创建一个对象
var person = create_person('张三'.toNativeUtf8(), 28);
print('${person.name.toDartString()}-----${person.age}');
//创建一个person的指针对象
var p = get_person('李四'.toNativeUtf8(), 18);
print("${p.ref.name.toDartString()} -----${p.ref.age}");
// 修改指针对象
p.ref.name = '王五'.toNativeUtf8();
p.ref.age = 20;
print("修改指针对象后====${p.ref.name.toDartString()} -----${p.ref.age}");
// 这里需要释放指针对象,这里建议C语言应该还需要提供一个释放的方法,供Dart调用。而不是直接在dart中直接释放
ffi.malloc.free(p);
}
- 结果:
create_person
方法返回了一个Dart的Person对象,这个是传值而非传引用,所以这个perosn对象相当于C语言中person对象的副本, 是分开的, 修改不会相互影响的。