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/: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// Darttypedef 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,这样写出来的代码会比较统一和整体,如果是增对复杂的业务逻辑处理,这个就得看实际情况了
如果觉得《Dart FFi》对你有帮助,请点赞、收藏,并留下你的观点哦!