在上一篇文章中,我们一起讨论了如何利用flutter官方提供的ffi库来绑定不同平台目录下的C源代码,那就是生成一个plugin类型的项目,然后在项目中指定平台目录下,根据不同平台的编译方式生成静态或者动态链接库,最后利用dart代码加载链接库后,再将本地方法符号转化为dart方法。
但是,这是老版本使用的方式,flutter3.0版本以后,官方提供了一种新的方式来集成C源代码功能,这次我们就来看看这个新方法-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
// Relative import to be able to reuse the C sources.information.
#include "../../src/hello.c"
android {
externalNativeBuild {
cmake {
path "../src/CMakeLists.txt"
}
}
}
plugin:
platforms:
android:
ffiPlugin: true
ios:
ffiPlugin: true
意思是利用ffiPlugin去为各个不同的平台编译源代码,并且绑定了二进制文件集成到flutter应用中去,你需要哪些平台都需要体现在这个配置项中。
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类,里面的方法与头文件中的方法存在映射关系。
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类中实现了具体的转换细节。