一步一步引导你掌握Dart ffi编写技巧

发表时间: 2023-02-28 15:10

本文以step by step的方式说明了Dart ffi的使用,适合新手学习。

作者 | 安秋亮(汘浪)

来源 | 阿里开发者


什么是ffi

ffi是 foreign function interface 的缩写,是一种机制。通过这种机制,用一种编程语言编写的程序可以调用另一种编程语言编写的程序或服务。像我们熟悉的Java JNI便是ffi机制。


创建sample工程

本文示例是在macos创建并运行的,最终的产物是mac可执行程序。

flutter --versionFlutter 3.3.0  channel stable  https://github.com/flutter/flutter.gitFramework  revision ffccd96b62 (6 weeks ago)  2022-08-29 17:28:57 -0700Engine  revision 5e9e0e0aa8Tools  Dart 2.18.0  DevTools 2.15.0

通过flutter create命令即可创建plugin[2]工程,以下命令中创建了名为plugin_ffi_sample的插件工程。

flutter create --template=plugin_ffi --platforms=macos plugin_ffi_sample

创建好工程后,执行以下命令即可编出macos的可执行文件并打开程序:

cd plugin_ffi_sample/exampleflutter run

接下来我们便基于plugin_ffi_sample工程进一步讲解ffi的使用。


plugin_ffi_sample 工程详解

在创建工程时指定了“--template=plugin_ffi”,所以plugin_ffi_sample是插件工程,子目录example是使用插件的示例工程。
plugin_ffi_sample/example/pubspec.yaml文件中有对plugin_ffi_sample插件的依赖配置。

dependencies:  flutter:    sdk: flutter  plugin_ffi_sample:    # When depending on this package from a real application you should use:    #   plugin_ffi_sample: ^x.y.z    # See https://dart.dev/tools/pub/dependencies#version-constraints    # The example app is bundled with the plugin so we use a path dependency on    # the parent directory to use the current plugin's version.    path: ../

有关插件工程的文档可以进一步参考[3]。


plugin_ffi_sample/example/lib/main.dart

我们节选了sample工程main.dart的部分代码如下,可见导入了插件工程中的plugin_ffi_sample.dart,并调用了sum和sumAsync方法。

import 'package:plugin_ffi_sample/plugin_ffi_sample.dart' as plugin_ffi_sample;//...class _MyAppState extends State<MyApp> {  late int sumResult;  late Future<int> sumAsyncResult;  @override  void initState() {    super.initState();    sumResult = plugin_ffi_sample.sum(1, 2); //    sumAsyncResult = plugin_ffi_sample.sumAsync(3, 4);//  }//...


plugin_ffi_sample/lib/plugin_ffi_sample.dart

节选部分代码如下:

可见sum方法的实现调用了_bindings对象中的sum方法,DynamicLibrary相关的代码用来打开动态库。

import 'plugin_ffi_sample_bindings_generated.dart';/// A very short-lived native function.////// For very short-lived functions, it is fine to call them on the main isolate./// They will block the Dart execution while running the native function, so/// only do this for native functions which are guaranteed to be short-lived.int sum(int a, int b) => _bindings.sum(a, b);//...const String _libName = 'plugin_ffi_sample';/// The dynamic library in which the symbols for [PluginFfiSampleBindings] 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 PluginFfiSampleBindings _bindings = PluginFfiSampleBindings(_dylib);


plugin_ffi_sample/lib/plugin_ffi_sample_bindings_generated.dart

注意
plugin_ffi_sample_bindings_generated.dart文件开头的注释,此文件是通过ffigen[4]工具自动生成的,实现了Dart到C语言的绑定。节选代码片段如下:

// AUTO GENERATED FILE, DO NOT EDIT.//// Generated by `package:ffigen`.import 'dart:ffi' as ffi;/// Bindings for `src/plugin_ffi_sample.h`.////// Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.///class PluginFfiSampleBindings {  /// Holds the symbol lookup function.  final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)      _lookup;  /// The symbols are looked up in [dynamicLibrary].  PluginFfiSampleBindings(ffi.DynamicLibrary dynamicLibrary)      : _lookup = dynamicLibrary.lookup;  /// The symbols are looked up with [lookup].  PluginFfiSampleBindings.fromLookup(      ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)          lookup)      : _lookup = lookup;  /// A very short-lived native function.  ///  /// For very short-lived functions, it is fine to call them on the main isolate.  /// They will block the Dart execution while running the native function, so  /// only do this for native functions which are guaranteed to be short-lived.  int sum(    int a,    int b,  ) {    return _sum(      a,      b,    );  }


头文件有变动时,重新执行以下命令就可以更新此文件:

flutter pub run ffigen --config ffigen.yaml

如果命令执行失败,请查看是否安装好llvm[5]:

brew install llvm


plugin_ffi_sample/ffigen.yaml是ffigen工具的配置文件,描述了根据哪些头文件生成ffi。

# Run with `flutter pub run ffigen --config ffigen.yaml`.name: PluginFfiSampleBindingsdescription: |  Bindings for `src/plugin_ffi_sample.h`.  Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.output: 'lib/plugin_ffi_sample_bindings_generated.dart'headers:  entry-points:    - 'src/plugin_ffi_sample.h'  include-directives:    - 'src/plugin_ffi_sample.h'


plugin_ffi_sample/src/plugin_ffi_sample.h

FFI_PLUGIN_EXPORT表示要导出符号,可见sum和sum_long_running的定义。

#if _WIN32#define FFI_PLUGIN_EXPORT __declspec(dllexport)#else#define FFI_PLUGIN_EXPORT#endif// A very short-lived native function.//// For very short-lived functions, it is fine to call them on the main isolate.// They will block the Dart execution while running the native function, so// only do this for native functions which are guaranteed to be short-lived.FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b);// A longer lived native function, which occupies the thread calling it.//// Do not call these kind of native functions in the main isolate. They will// block Dart execution. This will cause dropped frames in Flutter applications.// Instead, call these native functions on a separate isolate.FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b);

至此,你可以运行起Sample工程并结合代码观察ffi的执行过程。也可以对头文件接口上的参数或者返回值稍加修改,然后重新生成binding文件。其中sumAsync方法的实现值得深入阅读学习。


C/C++如何调用Dart

在上述工程中已经完整的演示了如何在Dart中调用C或者C++的方法。那么如何在C/C++中反调回Dart呢?

在这个示例中,我们将演示从Dart调用C/C++的ping方法,然后在C/C++中回调pong方法。

src/plugin_ffi_sample.h

在文件末尾添加函数指针定义以及ping方法:

typedef void (*pong) (void);FFI_PLUGIN_EXPORT void ping(pong callback);

这里是ping方法的实现,直接调用函数指针:

FFI_PLUGIN_EXPORT void ping(pong callback) {  printf("ping\n");  callback();}

执行ffigen命令重新生成绑定代码:

flutter pub run ffigen --config ffigen.yaml

文件末尾添加:

// C/C++要回调的必须是Top level的方法void pong() {  print("pong");}void ping() {  _bindings.ping(Pointer.fromFunction(pong));}

example/lib/main.dart

initState中插入ping方法调用

@override  void initState() {    super.initState();    plugin_ffi_sample.ping(); //ping    sumResult = plugin_ffi_sample.sum(1, 2);    sumAsyncResult = plugin_ffi_sample.sumAsync(3, 4);  }

重新执行Flutter run之后输出如下:

Building macOS application...                                           pingflutter: pongSyncing files to device macOS...                                   108ms


C/C++异步线程调用Dart

上述示例中我们演示了如何同步的回调Dart,但是在实战中我们经常会在C/C++开启子线程。那么如何在子线程中回调Dart呢?

为了快速编写异步线程回调的case,我们把plugin_ffi_sample.c改名为plugin_ffi_sample.cc,以方便使用C++编写代码,然后使用std::thread创建子线程回调Dart。

这里需要注意的是 extern "C" 的使用,是告诉编译器按C的方式进行编译。因为C++是多态的,支持重载,同名函数在编译后会生成特殊的符号,而C语言编译后的符号在Dart中可以直接按原函数名查找。

src/plugin_ffi_sample.cc

#include <thread>#ifdef __cplusplusextern "C" {#include "plugin_ffi_sample.h"#endif// A very short-lived native function.//// For very short-lived functions, it is fine to call them on the main isolate.// They will block the Dart execution while running the native function, so// only do this for native functions which are guaranteed to be short-lived.FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b) { return a + b; }// A longer-lived native function, which occupies the thread calling it.//// Do not call these kind of native functions in the main isolate. They will// block Dart execution. This will cause dropped frames in Flutter applications.// Instead, call these native functions on a separate isolate.FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b) {  // Simulate work.#if _WIN32  Sleep(5000);#else  usleep(5000 * 1000);#endif  return a + b;}void entry_point(pong call) {    printf("entry_point\n");    call();}FFI_PLUGIN_EXPORT void ping(pong callback) {    printf("ping\n");    pong p = callback;    std::thread* t = new std::thread(entry_point, p);    }#ifdef __cplusplus}#endif


macos/Classes/plugin_ffi_sample.c

// Relative import to be able to reuse the C sources.// See the comment in ../{projectName}}.podspec for more information.#include "../../src/plugin_ffi_sample.h"


点击查看原文,获取更多福利!

https://developer.aliyun.com/article/1114462?utm_content=g_1000368766


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。