手把手教你写 Dart ffi
本文以step by step的方式说明了Dart ffi的使用,适合新手学习。
什么是ffi
创建sample工程
flutter --version
Flutter 3.3.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ffccd96b62 (6 weeks ago) • 2022-08-29 17:28:57 -0700
Engine • revision 5e9e0e0aa8
Tools • Dart 2.18.0 • DevTools 2.15.0
flutter create --template=plugin_ffi --platforms=macos plugin_ffi_sample
cd plugin_ffi_sample/example
flutter run
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: ../
plugin_ffi_sample/example/lib/main.dart
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;
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
节选部分代码如下:
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
// 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
brew install llvm
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: PluginFfiSampleBindings
description: |
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
// 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);
C/C++如何调用Dart
typedef void (*pong) (void);
FFI_PLUGIN_EXPORT void ping(pong callback);
FFI_PLUGIN_EXPORT void ping(pong callback) {
printf("ping\n");
callback();
}
flutter pub run ffigen --config ffigen.yaml
// C/C++要回调的必须是Top level的方法
void pong() {
print("pong");
}
void ping() {
_bindings.ping(Pointer.fromFunction(pong));
}
void initState() {
super.initState();
plugin_ffi_sample.ping(); //ping
sumResult = plugin_ffi_sample.sum(1, 2);
sumAsyncResult = plugin_ffi_sample.sumAsync(3, 4);
}
Building macOS application...
ping
flutter: pong
Syncing files to device macOS... 108ms
C/C++异步线程调用Dart
extern "C" {
// 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.
Sleep(5000);
usleep(5000 * 1000);
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);
}
}
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.
src/CMakeLists.txt
# The Flutter tooling requires that developers have CMake 3.10 or later
# installed. You should not increase this version, as doing so will cause
# the plugin to fail to compile for some customers of the plugin.
cmake_minimum_required(VERSION 3.10)
project(plugin_ffi_sample_library VERSION 0.0.1 LANGUAGES C CXX)
set (CMAKE_CXX_STANDARD 11)
add_library(plugin_ffi_sample SHARED
"plugin_ffi_sample.cc"
)
set_target_properties(plugin_ffi_sample PROPERTIES
PUBLIC_HEADER plugin_ffi_sample.h
OUTPUT_NAME "plugin_ffi_sample"
)
target_compile_definitions(plugin_ffi_sample PUBLIC DART_SHARED_LIB)
cmake .
make
macos/plugin_ffi_sample.podspec
s.vendored_libraries = 'libplugin_ffi_sample.dylib'
ln -s ../src/libplugin_ffi_sample.dylib
lib/plugin_ffi_sample.dart
// C/C++要回调的必须是Top level的方法
void pong() {
- print("pong");
+ print("pong ${sum(3, 4)}");
}
../../third_party/dart/runtime/vm/runtime_entry.cc: 3766: error: Cannot invoke native callback outside an isolate.
解决方案
drwxr-xr-x 9 anql staff 288 7 13 06:23 .
drwxr-xr-x 10 anql staff 320 8 15 20:00 ..
-rw-r--r-- 1 anql staff 146111 7 13 06:33 dart_api.h
-rw-r--r-- 1 anql staff 2240 7 13 06:33 dart_api_dl.c
-rw-r--r-- 1 anql staff 7881 7 13 06:33 dart_api_dl.h
-rw-r--r-- 1 anql staff 6775 7 13 06:33 dart_native_api.h
-rw-r--r-- 1 anql staff 18150 7 13 06:33 dart_tools_api.h
-rw-r--r-- 1 anql staff 620 7 13 06:33 dart_version.h
drwxr-xr-x 3 anql staff 96 7 13 06:23 internal
dart_api_dl.h
/** \mainpage Dynamically Linked Dart API
*
* This exposes a subset of symbols from dart_api.h and dart_native_api.h
* available in every Dart embedder through dynamic linking.
*
* All symbols are postfixed with _DL to indicate that they are dynamically
* linked and to prevent conflicts with the original symbol.
*
* Link `dart_api_dl.c` file into your library and invoke
* `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`.
*/
DART_EXPORT intptr_t Dart_InitializeApiDL(void* data);
//...
/* Dart_Port */ \
F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object)) \
F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id)) \
F(Dart_SendPortGetId, Dart_Handle, \
(Dart_Handle port, Dart_Port_DL * port_id))
开始行动
将flutter sdk路径/flutter/bin/cache/dart-sdk/include拷贝到plugin_ffi_sample/src目录; src/CMakeLists.txt修改如下,这里主要是让“include/dart_api_dl.c”文件参与编译,以及指定include目录:
cmake_minimum_required(VERSION 3.10)
project(plugin_ffi_sample_library VERSION 0.0.1 LANGUAGES C CXX)
set (CMAKE_CXX_STANDARD 11)
add_library(plugin_ffi_sample SHARED
"include/dart_api_dl.c"
"plugin_ffi_sample.cc"
)
include_directories(include)
set_target_properties(plugin_ffi_sample PROPERTIES
PUBLIC_HEADER plugin_ffi_sample.h
OUTPUT_NAME "plugin_ffi_sample"
)
target_compile_definitions(plugin_ffi_sample PUBLIC DART_SHARED_LIB)
src/plugin_ffi_sample.h 变更如下,不再让函数指针作为ping方法的参数,而是让Main isolate的SendPort作为参数:
+
-FFI_PLUGIN_EXPORT void ping(pong callback);
+FFI_PLUGIN_EXPORT void ping(Dart_Port_DL main_isolate_send_port);
+
+FFI_PLUGIN_EXPORT intptr_t ffi_Dart_InitializeApiDL(void* data);
src/plugin_ffi_sample.cc 这里的实现会在C++的子线程中发送消息给Dart,需要注意的是,因为涉及不同的技术栈,所以这里的消息类型也是非常有限的。不过你可以设定一些协议进行通信。
-void entry_point(pong call) {
+void entry_point(Dart_Port_DL main_isolate_send_port) {
printf("entry_point\n");
- call();
+
+ Dart_CObject dart_object;
+ dart_object.type = Dart_CObject_kString;
+ dart_object.value.as_string = "pong";
+
+ const bool result = Dart_PostCObject_DL(main_isolate_send_port, &dart_object);
+ if (!result) {
+ printf("C : Posting message to port failed.\n");
+ }
}
-FFI_PLUGIN_EXPORT void ping(pong callback) {
+FFI_PLUGIN_EXPORT void ping(Dart_Port_DL main_isolate_send_port) {
printf("ping\n");
- pong p = callback;
- std::thread t(entry_point, p);
+ std::thread* t = new std::thread(entry_point, main_isolate_send_port);
}
+FFI_PLUGIN_EXPORT intptr_t ffi_Dart_InitializeApiDL(void* data) {
+ return Dart_InitializeApiDL(data);
+}
plugin_ffi_sample/macos/Classes/plugin_ffi_sample.c 这里不再需要了,因为我们通过CMake进行编译:
-
+// #include "../../src/plugin_ffi_sample.h"
plugin_ffi_sample/lib/plugin_ffi_sample.dart 这里主要是通过ReceivePort添加监听:
// C/C++要回调的必须是Top level的方法
-void pong() {
- print("pong ${sum(3, 4)}");
+void pong(dynamic msg) {
+ print("pong $msg");
}
void ping() {
- _bindings.ping(Pointer.fromFunction(pong));
+ final receivePort = ReceivePort()
+ ..listen((message) {
+ pong(message);
+ });
+ _bindings.ping(receivePort.sendPort.nativePort);
}
+
+int initializeApiDL() =>
+ _bindings.ffi_Dart_InitializeApiDL(NativeApi.initializeApiDLData);
plugin_ffi_sample/example/lib/main.dart 务必记得先初始化动态链接的api:
void initState() {
super.initState();
+ plugin_ffi_sample.initializeApiDL();
plugin_ffi_sample.ping(); //ping
sumResult = plugin_ffi_sample.sum(1, 2);
sumAsyncResult = plugin_ffi_sample.sumAsync(3, 4);
结语
在Dart中调用 String的 toNativeUtf8方法,务必记得传入Allocator对象,ffi方法执行完成后要释放内存;
NativeFinalizer 对象可以绑定Dart与C++对象,Dart对象被gc时可以回调指定的方法从而回收对象。
[1]https://en.wikipedia.org/wiki/Foreign_function_interface
[2]https://docs.flutter.dev/development/packages-and-plugins/developing-packages
[3]https://docs.flutter.dev/development/packages-and-plugins/developing-packages
[4]https://pub.dev/documentation/ffigen/latest/
[5]https://pub.dev/packages/ffigen
[6https://dart.dev/guides/language/concurrency]
[7]C interop using dart:ffi
https://dart.dev/guides/libraries/c-interop
[8]Binding to native Android code using dart:ff
https://docs.flutter.dev/development/platform-integration/android/c-interop
[9]Binding to native iOS code using dart:ffi
https://docs.flutter.dev/development/platform-integration/ios/c-interop
[10]Binding to native macOS code using dart:ffi
https://docs.flutter.dev/development/platform-integration/macos/c-interop
[11]ffigen | Dart Package
https://pub.dev/packages/ffigen
[12]ffigen - Dart API docs
https://pub.dev/documentation/ffigen/latest/
往期推荐
畅聊云栖
2022云栖大会一起见证科技创新,一起讨论云栖话题。
点击阅读原文查看详情。
微信扫码关注该文公众号作者