Dart FFi

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
DartLib
iOS
Android
lookupSymbol
open
if not found
open
if not found
cFunc
DartCall
DynamicLibrary
SymbolTable
Frameworks/*.dylib,*.framework
lib/*.so
processMainExecute
  1. Dart进入某个feature入口时,如果有使用到某个库,则将其加载到内存中,通过DynamicLibrary引用库文件句柄,这里的c方法也可以是main执行文件中全局符号
  2. 通过DynamicLibrary可以访问都改库的函数符号表并返回natiive的函数指针给dart侧
  3. Dart侧对Native函数的返回值和参数类型进行转换(这里的类型是有dartRuntime定义的)
  4. Dart使用对应的native函数执行native的代码
  5. 执行完毕后如果涉及到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用于自动生成dartc之间的绑定的代码. 不过目前这个插件还不能很好的兼容C++,尤其是class的调用。

总结

ffi相比较传统的JNI以及iOS的混编,减少了中间的一层包装,可以省去一些中间层封装代码。但需要进行类型匹配和定义,有一些额外的维护工作量,其次在内存管理上增加代码的复杂度,如果是是增对简单的借口或者有同样模版的命令调用可以考虑使用ffi,这样写出来的代码会比较统一和整体,如果是增对复杂的业务逻辑处理,这个就得看实际情况了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值