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。

集成步骤

  1. 跟之前一样,我们首先利用命令行创建项目:
flutter create --platforms=android,ios --template=plugin_ffi hello
  1. 添加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"
        }
    }
}
  1. 源代码的编译与绑定
    项目中的pubspec.yaml 提供了如下配置选项:
  plugin:
    platforms:
      android:
        ffiPlugin: true
      ios:
        ffiPlugin: true

意思是利用ffiPlugin去为各个不同的平台编译源代码,并且绑定了二进制文件集成到flutter应用中去,你需要哪些平台都需要体现在这个配置项中。

  1. 加载库与转换为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类,里面的方法与头文件中的方法存在映射关系。

  1. 调用方法
    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类中实现了具体的转换细节。

猜你喜欢

转载自blog.csdn.net/Yaoobs/article/details/129216628