本文以step by step的方式说明了Dart ffi的使用,适合新手学习。
ffi是 foreign function interface[1] 的缩写,是一种机制。通过这种机制,用一种编程语言编写的程序可以调用另一种编程语言编写的程序或服务。像我们熟悉的Java JNI便是ffi机制。
本文示例是在macos创建并运行的,最终的产物是mac可执行程序。
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命令即可创建plugin[2]工程,以下命令中创建了名为plugin_ffi_sample的插件工程。
flutter create --template=plugin_ffi --platforms=macos plugin_ffi_sample
创建好工程后,执行以下命令即可编出macos的可执行文件并打开程序:
cd plugin_ffi_sample/example
flutter run
接下来我们便基于plugin_ffi_sample工程进一步讲解ffi的使用。
在创建工程时指定了“--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: 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
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方法的实现值得深入阅读学习。
在上述工程中已经完整的演示了如何在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...
ping
flutter: pong
Syncing files to device macOS... 108ms
上述示例中我们演示了如何同步的回调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 __cplusplus
extern "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"
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)
make后会在当前目录生成动态库 libplugin_ffi_sample.dylib
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.
究其原因,可参看这里[6],只能在Flutter创建的isolate里执行Dart代码。
解决方案
通过Main isolate和Worker isolate通过message通信的逻辑得到启示,C++的线程既然不能直接调用Dart代码,那么能不能有一种C++给Dart isolate发送message的机制呢?
我们找到flutter sdk的安装路径,其中flutter/bin/cache/dart-sdk/include包含如下文件:
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
节选代码片段如下,可见有Dart_Post相关的api可用:
/** \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))
开始行动
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)
3 . src/plugin_ffi_sample.h 变更如下,不再让函数指针作为ping方法的参数,而是让Main isolate的SendPort作为参数:
+#include <dart_api_dl.h>
-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);
4 . 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);
+}
5 . plugin_ffi_sample/macos/Classes/plugin_ffi_sample.c 这里不再需要了,因为我们通过CMake进行编译:
-#include "../../src/plugin_ffi_sample.h"
+// #include "../../src/plugin_ffi_sample.h"
6 . 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);
7 . plugin_ffi_sample/example/lib/main.dart 务必记得先初始化动态链接的api:
@override
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);
至此,你已经掌握了基本的ffi用法,当然在实践中还会有更多需要注意的事项,这里再提两个知识点:
参考:
[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/
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/zmOH4p2932oaXzsF-MA5fA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。