一、简介
Dart FFI是可以在Dart Native平台上运行的Dart移动、命令行和服务器应用上通过Dart FFI来调用C代码的一个技术。
简单来说,就是Dart与C互相调用的一种机制。Dart FFI是Dart2.12.0版本后(同时包含在 Flutter 2.0 和以后的版本里),才作为稳定版本发布。
说到底,Dart语言也是因为Flutter使用了它才火起来的,所以Dart FFI技术在Flutter应用中更能发挥它更强大的作用。
参考链接:C interop using dart:ffi | Dart
1.1 解决的问题
- 可以同步调用C API,不像Flutter Channel一开始就是异步
- 调用C语言更快,不像之前需要通过Native中转(或者改Flutter引擎代码)
- 还可以封装替换Flutter Channel达到更快和支持同步的目地
二、常用属性与方法
为了连通Dart与C语言,Dart FFI提供了很多方法,下面介绍一下主要的方法。
2.1 DynamicLibrary(库管理)
2.1.1 open
它可以加载动态链接库
external factory DynamicLibrary.open(String path); |
此方法用于加载库文件,如上面我编译C后生成的libsample.dylib
文件,我们需要使用此方法来将其加载到DartVM中。需要注意的是,多次调用此方法加载库文件也只会将库文件加载到DartVM中一次。
代码示例
import 'dart:ffi' as ffi; import 'package:path/path.dart' as path; var libraryPath = path.join( Directory.current.path, 'library', 'build', 'libsample.dylib'); final dylib = ffi.DynamicLibrary.open(libraryPath); |
2.1.2 executable
它可用于加载静态链接库
external factory DynamicLibrary.executable(); |
2.1.3 process
external factory DynamicLibrary.process(); |
它可以用于在iOS及MacOS中加载应用程序已经自动加载好的动态链接库,也可以解析静态链接到应用的二进制文件符号。需要注意的是,它不能用于windows平台。
2.1.4 lookup
它用于在DynamicLibrary中查找到对应的符号并返回其内存地址。
external Pointer<T> lookup<T extends NativeType>(String symbolName); |
操作示例
final dylib = DynamicLibrary.open(libraryPath); late final _hello_worldPtr = dylib.lookup<NativeFunction<Void Function()>>('hello_world'); late final _hello_world = _hello_worldPtr.asFunction<void Function()>(); _hello_world(); |
2.2 NativeType(类型映射)
NativeType是在Dart中表示C语言中的数据结构,它不可在Dart中实例化,只能由Native返回。
Dart FFI与C基础数据类型映射表如下
2.3 Pointer(指针)
它是C语言中指针在Dart中的映射。
2.3.1 fromAddress
根据内存地址获取C对象指针
// 创建一个指向NULL的Native指针 final Pointer<Never> nullptr = Pointer.fromAddress(0); |
2.3.2 fromFunction
根据一个Dart函数,创建一个Native函数指针,一般用于将Dart函数传给C,使C有调用Dart函数的能力
void globalCallback(int src, int result) { print("globalCallback src=$src, result=$result"); } Pointer.fromFunction(globalCallback); |
2.3.3 address
获取指针的内存地址
2.3.4 asFunction
将Native指针对象,转换为Dart函数
2.3.5 sizeOf
返回具体类型的内存占用
ffi.sizeOf<ffi.Int64>(); // 8 |
2.4 malloc(内存管理)
2.4.1 allocate
开辟一块大小byteCount
的空间
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}); |
代码示例
Pointer<Uint8> bytes = malloc.allocate<Uint8>(ffi.sizeOf<ffi.Uint8>()); |
2.4.2 free
释放内存
malloc.free(bytes); |
三. 参考示例
3.1 C中实现Dart函数回调
面我们通过示例来了解一下C如何调用Dart函数。
原理: C本身是没有提供调用Dart函数的方法的,但是我们可以在程序启动后通过Dart将函数当做参数传入C中,C中缓存起来Dart的函数指针,就可以在需要的时候实现C调用Dart。
1. 定义dart函数
我们先在Dart上定义一个函数。需要注意的是Dart函数需要是顶级函数或者静态函数才能被调用,否则会报错。
void dartFunction() { debugPrint("[Dart]: Dart 函数被调用了"); } |
2. 编写c文件
定义一个注册函数 sample.h
void callDart(void (*callback)()); |
sample.c 实现
void callDart(void (*callback)()) { printf("[CPP]: 现在调用Dart函数"); callback(); } |
其中的callback就是接收到的Dart的函数,这里我们为了看效果,就在注册后直接调用Dart函数了。
然后我们将Dart函数转换成Pointer
类型,并通过调用C的callDart
函数传入到C中。
3. 编写调用代码
late final _callDartPtr = _lookup< ffi.NativeFunction< ffi.Void Function( ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>>( 'callDart'); late final _callDart = _callDartPtr.asFunction< void Function(ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(); _callDart(ffi.Pointer.fromFunction(dartFunction)); |
这里,我们试用结果ffi.Pointer.fromFunction
方法将Dart函数转换成C函数指针的Dart映射,然后通过_callDart
来调用C的callDart
函数。
4. 输出结果
[CPP]: 现在调用Dart函数 [Dart]: Dart 函数被调用了 |
3.2 Dart调C
1. 无传参无返回值
我们通过一个例子,让Dart来调用C的函数,并在C的函数中输出一句话。
sample.h
void hello_world(); |
sample.c
void hello_world() { printf("[CPP]: Hello World"); } |
ffi_sample.dart
late final _hello_worldPtr = _lookup<ffi.NativeFunction<ffi.Void Function()>>('hello_world'); late final _hello_world = _hello_worldPtr.asFunction<void Function()>(); print('[Dart]: ${_hello_world()}'); |
结果输出
[CPP]: Hello World [Dart]: null |
2. 有返回值
当C有返回值时,可以通过类型转换接收 sample.h
char* getName(); |
sample.c
char* getName() { return "My name is 大哥大"; } |
ffi_sample.dart
late final _getNamePtr = _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Int8> Function()>>('getName'); late final _getName = _getNamePtr.asFunction<ffi.Pointer<ffi.Int8> Function()>(); print("[Dart]: 有返回值 -> "+_getName().cast<Utf8>().toDartString()); |
输出结果
[Dart]: 有返回值 -> My name is 大哥大 |
3. 有传参
利用C的printf
函数,实现一个Dart打印函数
sample.h
void cPrint(char *str); |
sample.c
void cPrint(char *str) { printf("[CPP]: %s", str); free(str); } |
ffi_sample.dart
late final _cPrintPtr = _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Int8>)>>( 'cPrint'); late final _cPrint = _cPrintPtr.asFunction<void Function(ffi.Pointer<ffi.Int8>)>(); _cPrint("我认为这个输出很有意义".toNativeUtf8().cast<ffi.Int8>()); |
输出结果
[CPP]: 我认为这个输出很有意义 |