flutter项目中 如何开发FFI类型的插件
flutter 项目中如何开发FFI类型的插件
flutter项目中 如何开发FFI类型的插件
前言
在上一篇文章中,我们一起讨论了如何利用flutter官方提供的ffi库来绑定不同平台目录下的C源代码,那就是生成一个plugin类型的项目,然后在项目中指定平台目录下,根据不同平台的编译方式生成静态或者动态链接库,最后利用dart代码加载链接库后,再将本地方法符号转化为dart方法。
但是,这是老版本使用的方式,flutter3.0版本以后,官方提供了一种新的方式来集成C源代码功能,这次我们就来看看这个新方法-FFI plugin。
FFI plugin
上图是官方文档中对于FFI plugin的简单描述,可以看到,FFI plugin是专门为绑定本地源代码而设计出来的,常规plugin虽然也可以支持,但是主要用途还是支持method channel,即dart调用各种相关平台的 API(Android 中的 Java 或 Kotlin API,iOS 中的 Objective-C 或 Swift API,Windows 操作系统中的 C++ API),而且官方的意思是3.0之后对C源代码功能的支持ffi plugin会更强大,所以我们如果只是调用C代码,不需要平台SDK API的话,可以考虑使用FFI plugin。
集成步骤
- 跟之前一样,我们首先利用命令行创建项目:
flutter create --platforms=android,ios --template=plugin_ffi hello
- 添加C/C++源码以及相关编译配置文件:
创建完成后,我们观察一下FFI plugin 项目的目录结构,对比常规plugin,主要有以下几点不同:
本地的源代码文件和CmakeFile.txt文件现在统一放到项目的src目录下,ios平台目录Classes下面的源文件存在,只是引入了src下面的源代码,android平台build.gradle 文件中externalNativeBuild属性中cmake的路径也是指向src中的CmakeFile.txt。
// Relative import to be able to reuse the C sources.information.
#include "../../src/hello.c"
android {
externalNativeBuild {
cmake {
path "../src/CMakeLists.txt"
}
}
}
- 源代码的编译与绑定
项目中的pubspec.yaml 提供了如下配置选项:
plugin:
platforms:
android:
ffiPlugin: true
ios:
ffiPlugin: true
意思是利用ffiPlugin去为各个不同的平台编译源代码,并且绑定了二进制文件集成到flutter应用中去,你需要哪些平台都需要体现在这个配置项中。
- 加载库与转换为dart方法
ffiPlugin项目为我们提供了一种方式,让我们可以利用源代码根据一定的转化规则自动生成dart的方法,这个是通过ffigen.yaml文件与ffigen命令去完成的:
flutter pub run ffigen --config ffigen.yaml
ffigen.yaml内容如下:
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: HelloBindings
description: |
Bindings for `src/hello.h`.
Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
output: 'lib/hello_bindings_generated.dart'
headers:
entry-points:
- 'src/hello.h'
include-directives:
- 'src/hello.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
comments:
style: any
length: full
src/hello.h如下:
...
#if _WIN32
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FFI_PLUGIN_EXPORT
#endif
FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b);
FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b);
...
import 'dart:ffi' as ffi;
class HelloBindings {
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
HelloBindings(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
int sum(
int a,
int b,
) {
return _sum(
a,
b,
);
}
late final _sumPtr =
_lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr)>>(
'sum');
late final _sum = _sumPtr.asFunction<int Function(int, int)>();
...
}
可以看出,它是根据头文件中定义的本地方法自动生成了dart代码,这个代码文件中有一个HelloBindings类,里面的方法与头文件中的方法存在映射关系。
- 调用方法
lib/hello.dart文件如下:
const String _libName = 'hello';
/// The dynamic library in which the symbols for [HelloBindings] can be found.
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
/// The bindings to the native functions in [_dylib].
final HelloBindings _bindings = HelloBindings(_dylib);
int sum(int a, int b) => _bindings.sum(a, b);
在这个文件里,我们还是要通过DynamicLibrary来加载本地库文件,再将实例传到类的构造方法中,调用sum方法时在HelloBindings类中实现了具体的转换细节。
更多推荐
所有评论(0)