在flutter的1.10.x后的分支,dart:ffi被并入flutter,现在flutter中也可以使用ffi了。这东西是啥玩意呢,就是让dart可以直接调用cc代码等东西的库
在 flutter 的 1.10.x 后的分支, dart:ffi 被并入 flutter, 现在 flutter 中也可以使用 ffi 了。 这东西是啥玩意呢, 就是让 dart 可以直接调用 c/c++ 代码等东西的库, FFI(foreign function interface), 官方文档在这里。
但是在当前版本中, 这东西在官方说明中依然处于技术预览版, 就是可用, 但后续不保证 api 不变更。
文章目录 开发环境 ffi 的简单介绍 在 flutter 中使用 创建仓库 cpp 文件 dart 文件 ios Android 简单总结 后记
开发环境 首先我是 mac 系统, windows 系统不保证脚本的可用和工具的可用, linux 的话可能一些必要工具需要使用自己平台的包管理工具, 并且涉及到 ios 部分, 必须使用 mac。
所有需要的工具包
Xcode(或 XcodeBuild 命令行工具) brew clang cmake Android 工具链 Android SDK NDK Android Studio(可选) Gradle Flutter 工具链 vscode(可选, 这东西看你的情况,作为示例的话只要是文本编辑器即可, 我本人使用这个作为主要的文本编辑器) 这里说的是包含后续所有用到的东西, 并不仅仅是本文。 其中对于 flutter 开发者可能需要单独安装的应该只有 NDK 和 Cmake, 这两个东西是包含在 android sdk 下的, 可以使用 android studio 下载, 也可以单独下载
ffi 的简单介绍 根据官方文档说明
可以理解为, 将 c 的类型和 dart 的类型关联起来, 然后 ffi 会在内部将两端关联起来, 完成调用
有如下几种类型
基本就是对应 c 中的类型, 对应 Void 各种长度的 有无符号的整型, 单双精度浮点, 指针, 方法
转化的过程 c 源码核心就这点, 其他的都做不知即可
void hello_world ( ) { printf ( "Hello World\n" ) ; }
导包, 这个是第一步要做的
import 'dart:ffi' as ffi;
// 定义一个ffi类型 typedef hello_world_func = ffi.Void Function();// 将ffi类型定义为dart类型 typedef HelloWorld = void Function();// 打开动态库, dylib是mac上的动态库的后缀 final dylib = ffi.DynamicLibrary.open('hello_world.dylib');// 这里是最难理解的一步, 后面会详细解说 final HelloWorld hello = dylib.lookup>('hello_world').asFunction();// 调用 hello();
详细理解转化过程 这里以 lookup 方法为切入点,详细理解下这里做了什么, 以便于后面我们可以自行完成这个过程
lookup 方法签名如下:
external Pointer lookup(String symbolName);
参数 很好理解, 传入一个方法名, 让我们能找到 c 方法
泛型 这个是方法的类型签名的 dart:ffi 表现形式.
c 方法的签名是这样的: void hello_world()
, 所以我们就需要一个对应的类型, 也就是上面定义的 ffi 类型
ffi.Void Function()
返回类型 这里的返回值是用于在实际调用时,转化 c 方法的返回值为 dart 的类型来使用的, 所以就是对应的 dart 类型
/// 定义是这样的 void Function()/// 接收的asFunction方法 final void Function() hello = XXXX;
写起来的时候可能是这样的,
实例 extern "C" { int32_t native_add ( int32_t x, int32_t y) { return x + y; } double double_add ( double x, double y) { return x + y; } }
import 'dart:ffi';final DynamicLibrary dylib = Platform.isAndroid? DynamicLibrary.open("libnative_add.so"): DynamicLibrary.open("native_add.framework/native_add");final int Function(int x, int y) nativeAdd = dylib.lookup>("native_add").asFunction();final double Function(double, double) doubleAdd = dylib.lookup>("double_add").asFunction();
打包和运行 在 dart vm 中,可以有多种方案, 只要能编译出 dylib 即可
官方的hello world 示例中是直接使用 make, 内部使用 gcc 打包编译
这里有一个脚本,是设置 dylib 的目录到环境变量中, 以便于运行时可以找到动态库
在 flutter 中使用 接着就要开始在 flutter 中使用了, 和在 dart vm 中使用不一样, 不能使用环境变量, 而是需要将库置入到项目中
创建仓库 直接使用 $ flutter create -t plugin native_add
的方式即可
cpp 文件 native_add.cpp
#include extern "C" { int32_t native_add ( int32_t x, int32_t y) { return x + y; } double double_add ( double x, double y) { return x + y; } }
dart 文件 final DynamicLibrary dylib = Platform.isAndroid? DynamicLibrary.open("libnative_add.so"): DynamicLibrary.open("native_add.framework/native_add");final int Function(int x, int y) nativeAdd = dylib.lookup>("native_add").asFunction();final double Function(double, double) doubleAdd = dylib.lookup>("double_add").asFunction();
界面:
class MyHomePage extends StatefulWidget {MyHomePage({Key key, this.title}) : super(key: key);final String title;@override_MyHomePageState createState() => _MyHomePageState(); }class _MyHomePageState extends State {int _counter = 0;void _incrementCounter() {setState(() {_counter = nativeAdd(_counter, 1);});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('You have pushed the button this many times:',),Text('$_counter',style: Theme.of(context).textTheme.display1,),Text("native double value = ${doubleAdd(_counter.toDouble(), _counter.toDouble())}"),],),),floatingActionButton: FloatingActionButton(onPressed: _incrementCounter,tooltip: 'Increment',child: Icon(Icons.add),),);} }
ios ios 中, 直接将 cpp 文件置入 ios/classes 文件夹内即可, 然后因为 podspec 中包含默认配置的原因, 这个文件会被自动引入项目
s. source_files = 'Classes/**/*'
运行项目:
Android android 中其实有两种方法, 一是用传统的 ndk 方式, 就是 Android.mk 那种方案, 我们略过这种方案, 因为配置比较复杂, 我们使用第二种方案, 官方推荐的 cmake 方案
因为 ios 中, 文件被置入源码中, 我这里直接使用相对路径去引入这个文件
CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1) # for exampleadd_library( native_add# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).../ios/Classes/native_add.cpp )
指定源码对应的库是哪个库 指定库的类型, 这里是动态库, 所以用 SHARED 指定源码目录 然后因为我们使用了 cmake, 为了让安卓项目知道, 我们需要修改 gradle 文件
android{// ...externalNativeBuild {cmake {path "CMakeLists.txt"}} }
这里在 android 节点下, 添加属性即可, 这里是指定 Cmake 使用的文件
接着就可以运行项目了, 和 android 中一样
简单总结 现在 ffi 处于初始阶段, 还有诸多不足.
比如, 文档的缺失, 现在如何传递字符串,数组都是问题, 虽然有结构体的定义, 也能看到部分说明, 但没有简单的示例帮助开发者快速使用.
只有基本数据类型, 目前可能还不需要借用 c 来解决, 未来则要看 ffi 会开放到什么程度.
后记 项目地址: https://github.com/caijinglong/example_for_flutter_ffi
以上