Dart FFi
FFI: 全称Foregin Function Interface,外部函数接口,是dart sdk的一个新的语言特性,提供了一套动态库加载器,能直接查找内存中已有的函数符号,也支持将指定路径的库加加载到内存中.通在dart侧持有这个库的文件句柄,利用dart暴露的lookupSymbol
方法查找对应的native方法返回给dart调用,从而提交与native通信的效率,相比较JNI和iOS的混编,它为开发者提供了一套额外的技术方案.
示例
实例代码可以参考 https://dart.dev/guides/libraries/c-interop这里的例子,官方给出了4个示例
hello_world: 通过dart调用一个基本的native无返回值无参数的使用方法.
primitives: 一个简易的数值计算,主要包括了参数和返回值的指针传递,以及在使用后销毁它们
structs: 一个操作native结构体对象的实例,介绍了如何将native的结构体转换成dart端的可操作的对象
sqlite: 基于ffi封装的mini版本的sqlite
执行流程
- Dart进入某个feature入口时,如果有使用到某个库,则将其加载到内存中,通过
DynamicLibrary
引用库文件句柄,这里的c方法也可以是main执行文件中全局符号 - 通过
DynamicLibrary
可以访问都改库的函数符号表并返回natiive的函数指针给dart侧 - Dart侧对Native函数的返回值和参数类型进行转换(这里的类型是有dartRuntime定义的)
- Dart使用对应的native函数执行native的代码
- 执行完毕后如果涉及到Dart新创建的指针类型,需要由dart去手动释放
Note:
- 在windows上不支持获取主程序符号表(
processMainExecute
)
/// Creates a dynamic library holding all global symbols.
///
/// Any symbol in a library currently loaded with global visibility
/// (including the executable itself) may be resolved through this library.
///
/// This feature is not available on Windows.
external factory DynamicLibrary.process();
- 如果加载的库文件比较大,可以通过创建一个新的
isolate
来加载
DynamicLibrary实现
在~/versions/2.5.3/bin/cache/dart-sdk/lib/ffi/dynamic_library.dart
:可以找到它的具体实现,通过调用系统库函数生成文件句柄,返回给dart用于查找符号,同时利用vm定义的dart基本数据类型实现和c基本类型之间的转换
runtime/lib/ffi_dynamic_library.cc:
71
72: DEFINE_NATIVE_ENTRY(Ffi_dl_open, 0, 1) {
73 GET_NON_NULL_NATIVE_ARGUMENT(String, lib_path, arguments->NativeArgAt(0));
// 加载动态库,返回文件句柄
static void* LoadExtensionLibrary(const char* library_file) {
...
void* handle = dlopen(library_file, RTLD_LAZY);
...
return handle;
// 根据动态库句柄和符号,获取函数地址,提供给dart层调用
static void* ResolveSymbol(void* handle, const char* symbol) {
...
void* pointer = dlsym(handle, symbol);
原生工程的集成
- android通过ndk引入
c/c++
的代码生成libxx.so
文件
DynamicLibrary.open('libsqlcipher.so');
iOS则通过embbed
或者直接link到主执行文件中
DynamicLibrary.open('sqlite3.framework/sqlite3');
flutter侧通过libxx
的名字就能加载该库
FFi自动转换
由于所有的函数和使用类型都需要进行C和dart的转换,定义的函数会翻倍,所以当业务庞大后,代码量会很大,而且手动编写容易错误.
// C multi sum function - int multi_sum(int nr_count, ...);
//
// Example of how to call C functions with varargs with a fixed arg count in
// Dart
typedef MultiSumFunc = Int32 Function(
Int32 numCount, Int32 a, Int32 b, Int32 c);
typedef MultiSum = int Function(int numCount, int a, int b, int c);
// example calling a C function with varargs
// calls int multi_sum(int nr_count, ...);
final multiSumPointer =
dylib.lookup<NativeFunction<MultiSumFunc>>('multi_sum');
final multiSum = multiSumPointer.asFunction<MultiSum>();
print('3 + 7 + 11 = ${multiSum(3, 3, 7, 11)}');
官方推出了一个插件ffigen用于自动生成dart
与c
之间的绑定的代码. 不过目前这个插件还不能很好的兼容C++,尤其是class的调用。
总结
ffi相比较传统的JNI以及iOS的混编,减少了中间的一层包装,可以省去一些中间层封装代码。但需要进行类型匹配和定义,有一些额外的维护工作量,其次在内存管理上增加代码的复杂度,如果是是增对简单的借口或者有同样模版的命令调用可以考虑使用ffi,这样写出来的代码会比较统一和整体,如果是增对复杂的业务逻辑处理,这个就得看实际情况了