用 ffigen 构建 FFI 风格的 Flutter 插件:path_provider_foundation 的绑定生成与严格过滤实践
用 ffigen 构建 FFI 风格的 Flutter 插件path_provider_foundation 的绑定生成与严格过滤实践【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages本指南以 Flutter 官方仓库中的path_provider_foundation包为对象讲解一种不依赖原生插件结构、而是借助ffigen直接生成 Foundation 框架 FFI 绑定的插件实现方式。读完本文你将掌握如何修改 tool/ffigen.dart 并重新生成绑定、ffigen 的接口/成员/函数三层白名单过滤如何配置、以及生成的绑定在运行时如何被真正的路径查询逻辑消费。背景为什么 path_provider 的 iOS/macOS 实现不走标准插件结构path_provider_foundation是 Flutter 官方 federated 插件path_provider在 iOS 与 macOS 上的实现包。按 README.md 的说明它是一个endorsed背书插件应用只需依赖path_provider该实现包会被自动引入无需手工加入pubspec.yaml只有当你想直接使用本包的 API 时才需要显式声明依赖。与绝大多数 Flutter 插件不同本包不使用“Dart 层 原生 Swift/Objective-C 宿主代码”的标准结构——它既没有ios/目录也没有macos/原生源码目录。从 pubspec.yaml 可以看到它通过dartPluginClass: PathProviderFoundation声明纯 Dart 插件实现运行时依赖三个关键库ffi^2.1.4Dart FFI 基础设施objective_c^9.2.1Objective-C 运行时绑定让 Dart 可以直接操作NSString、NSURL、NSArray等 ObjC 对象path_provider_platform_interface^2.1.0实现PathProviderPlatform平台接口所需。flutter: plugin: implements: path_provider platforms: ios: dartPluginClass: PathProviderFoundation macos: dartPluginClass: PathProviderFoundation也就是说这个包用Dart FFI Objective-C 互操作直接调用 Foundation 框架替代了在 Swift/ObjC 中编写 MethodChannel 宿主代码的传统做法。核心工作流修改 ffigen.dart重新生成绑定按 CONTRIBUTING.md 的说明本包使用ffigen调用 Foundation 方法。为 FFI 接口新增功能的唯一入口是编辑配置脚本 tool/ffigen.dart在包根目录运行dart run tool/ffigen.dartffigen是dev_dependencies中的开发期依赖仓库当前锁定ffigen: ^20.0.0见 pubspec.yaml它解析 macOS SDK 中的头文件输出 Dart 绑定代码到 lib/src/ffi_bindings.g.dart。该生成文件首行明确写着AUTO GENERATED FILE, DO NOT EDIT.因此所有改动都必须回到ffigen.dart配置中进行不能直接手改生成结果。深入解析 tool/ffigen.dart 的配置ffigen.dart 的main()构造了一个FfiGenerator其配置可拆成五层来理解。1. 输出配置Outputoutput: Output( dartFile: packageRoot.resolve(lib/src/ffi_bindings.g.dart), style: const DynamicLibraryBindings( wrapperName: FoundationFFI, wrapperDocComment: Bindings for NSFileManager., ), ),输出文件固定为lib/src/ffi_bindings.g.dartDynamicLibraryBindings风格会生成一个名为FoundationFFI的包装类生成结果见 ffi_bindings.g.dart#L10-L44该类持有符号查找函数供运行时代码统一调用。2. 头文件入口Headersheaders: Headers( entryPoints: Uri[ Uri.file( $macSdkPath/System/Library/Frameworks/AVFAudio.framework/Headers/AVAudioPlayer.h, ), ], ),入口头文件指向 macOS SDK 中的AVFAudio.framework/Headers/AVAudioPlayer.h其中$macSdkPath是 ffigen 提供的、解析当前激活 SDK 路径的变量需要已安装 Xcode 命令行工具。从生成结果看ffigen 实际解析到了 Foundation 框架中声明的NSFileManager、NSURL等类型——这是因为该入口头文件会传递性地导入 Foundation随后再由下面的过滤器决定哪些声明真正进入生成代码。3. Objective-C 接口过滤interfaces / include / includeMemberobjectiveC: ObjectiveC( interfaces: Interfaces( include: (Declaration declaration) { return String{NSFileManager, NSURL}.contains(declaration.originalName); }, includeMember: (Declaration declaration, String member) { final String interfaceName declaration.originalName; final signature member; return switch (interfaceName) { NSFileManager String{ containerURLForSecurityApplicationGroupIdentifier:, defaultManager, }.contains(signature), NSURL String{ fileURLWithPath:, URLByAppendingPathComponent:, }.contains(signature), _ false, }; }, ), ... ),这是两层白名单include限定只保留NSFileManager与NSURL两个类includeMember再按成员签名精确到方法/属性级。例如NSFileManager只保留defaultManager获取单例与containerURLForSecurityApplicationGroupIdentifier:App Group 容器路径NSURL只保留fileURLWithPath:与URLByAppendingPathComponent:。对照生成代码 ffi_bindings.g.dartNSFileManager确实只有getDefaultManager()与containerURLForSecurityApplicationGroupIdentifier:两个 API。4. Category 过滤categoriescategories: Categories( include: (Declaration declaration) String{ // For URLByAppendingPathComponent: NSURLPathUtilities, }.contains(declaration.originalName), includeTransitive: false, ),URLByAppendingPathComponent:实际定义在NSURL的 categoryNSURLPathUtilities中因此需要显式放行该 category并设置includeTransitive: false关闭传递包含避免把 category 引入的其它 API 一并带出。5. 顶层函数过滤functionsfunctions: Functions.includeSet(String{NSSearchPathForDirectoriesInDomains}),C 函数层只保留NSSearchPathForDirectoriesInDomains——这是 iOS/macOS 查询标准目录Documents、Caches、Library 等的核心 C API对应生成代码 ffi_bindings.g.dart#L22-L44 中的同名方法。同时ffigen 会为NSSearchPathDirectory生成带原始数值的枚举如NSDocumentDirectory(9)、NSCachesDirectory(13)、NSApplicationSupportDirectory(14)、NSDownloadsDirectory(15)见 ffi_bindings.g.dart#L46-L108运行时直接以 Dart 枚举传入。配置哲学为什么要坚持“严格过滤”CONTRIBUTING.md 用专节阐述了本包的配置哲学可归纳为两点保持包体积最小只生成实际用到的符号避免把 Foundation 数千个 API 全部带进产物避免生成需要原生辅助代码的绑定ffigen 对某些 API 会生成依赖 native code helper 的包装例如需要 C 回调、需要额外内存管理的场景一旦混入这类绑定就必须为插件搭建原生编译步骤彻底破坏“纯 Dart FFI 实现”的架构前提。因此所有include/includeMember/includeSet都采用白名单语义宁缺毋滥。新增功能时先在真实实现中确认它确实只需要这些符号再在ffigen.dart中精确加入对应的方法签名。生成的绑定在运行时如何被使用绑定生成后由 lib/src/path_provider_foundation_real.dart 消费。其运行时骨架分三步final ffi.DynamicLibrary _dylib () { return ffi.DynamicLibrary.open(/System/Library/Frameworks/Foundation.framework/Foundation); }(); final FoundationFFI _lib () { return FoundationFFI(_dylib); }();即通过DynamicLibrary.open直接加载系统 Foundation 动态库见 path_provider_foundation_real.dart#L152-L159再用生成的FoundationFFI包装类完成符号查找。所有目录查询最终汇聚到_getUserDirectory以NSUserDomainMask域调用NSSearchPathForDirectoriesInDomains并取首个结果path_provider_foundation_real.dart#L125-L133NSString? _getUserDirectory(NSSearchPathDirectory directory) { final NSArray paths _ffiLib.NSSearchPathForDirectoriesInDomains( directory, NSSearchPathDomainMask.NSUserDomainMask, true, ); final ObjCObject? first paths.firstObject; return first null ? null : NSString.as(first); }在此基础上path_provider_foundation_real.dart#L104-L122 的_getDirectoryPath还包含一个关键的平台差异处理macOS 上Application Support 与 Caches 目录会再追加 Bundle Identifier 子路径通过NSURL.URLByAppendingPathComponent:拼接取NSBundle.getMainBundle().bundleIdentifieriOS 上不做该处理以保持与旧版插件兼容。而getApplicationSupportPath与getApplicationCachePath在返回前会调用Directory(path).create(recursive: true)保证目录真实存在与其它平台行为一致。App Group 支持是本包另一亮点getContainerPath通过NSFileManager.getDefaultManager().containerURLForSecurityApplicationGroupIdentifier:查询 App Group 容器路径且仅限 iOS在 macOS 上会抛出UnsupportedError见 path_provider_foundation_real.dart#L95-L102。此外入口文件 lib/path_provider_foundation.dart 使用条件导出支持dart.library.ffi的平台走真实实现否则导出 lib/src/path_provider_foundation_stub.dart 中的同名 stub如 Web避免传递依赖破坏 Web 编译。测试策略单元测试与真实运行时的分工本包的测试很好地印证了 FFI 方案的边界。普通 Dart 单元测试无法创建 Objective-C 对象因此 test/path_provider_foundation_test.dart 只覆盖“不需要 ObjC 运行时”的行为——通过注入FakeFoundationFFI与FakePlatformProvider验证三个外部存储接口在 iOS/macOS 上均抛出UnsupportedError、以及getContainerPath在 macOS 上抛错。凡是需要真实 ObjC 对象的测试都放在 example/integration_test/path_provider_test.dart端到端组直接调用PathProviderPlatform.instance的各个getXxxPath并在返回目录中实际写读文件验证路径可用单元组用MockFoundationFFImockito 生成stub 掉NSSearchPathForDirectoriesInDomains分别在FakePlatformProvider(isIOS: true)与isMacOS: true两种变体下断言返回路径其中 macOS 变体明确断言路径末尾追加了dev.flutter.plugins.pathProviderExample这一 Bundle ID与实现逻辑一一对应。PathProviderFoundation的构造函数通过visibleForTesting参数platform、ffiLib、containerURLForSecurityApplicationGroupIdentifier开放依赖注入这正是上述测试能够无缝 mock 的原因。为 FFI 接口新增功能的实践清单综合 CONTRIBUTING.md 与源码结构为path_provider_foundation增加新能力时建议遵循在 tool/ffigen.dart 的对应过滤器中精确白名单新增符号类级加interfaces.include、成员级加includeMember、C 函数加functions.includeSet、涉及 category 则放行对应 category 并保持includeTransitive: false在包根目录执行dart run tool/ffigen.dart让 lib/src/ffi_bindings.g.dart 重新生成该文件不可手改在 path_provider_foundation_real.dart 中通过注入的FoundationFFI实例调用新绑定并在 path_provider_foundation_stub.dart 中同步补全对应 stub API无需 ObjC 运行时的逻辑写入 test/ 单元测试需要真实 ObjC 对象的行为写入 example/integration_test/ 集成测试并分别覆盖 iOS/macOS 两种平台变体尤其注意 macOS 的 Bundle ID 子路径规则。这套“ffigen 配置 → 生成绑定 → 纯 Dart 运行时消费 → 分层测试”的实践既保证了插件包体积可控也彻底免去了原生编译步骤可作为需要直接调用系统框架 API 的 Dart 侧插件实现的有益参考。【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Camunda Platform 测试指南:从单元测试规则到集成测试与多数据库验证的完整实践

Camunda Platform 测试指南:从单元测试规则到集成测试与多数据库验证的完整实践

Camunda Platform 测试指南:从单元测试规则到集成测试与多数据库验证的完整实践 【免费下载链接】camunda-bpm-platform Camunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 En…

2026/9/22 5:49:24 阅读更多 →
IntelliJ IDEA安装配置教程:JDK、Tomcat、插件与避坑

IntelliJ IDEA安装配置教程:JDK、Tomcat、插件与避坑

新来的同事上周装 IntelliJ IDEA,从下载到能把项目跑起来整整折腾了三天,卡的地方说起来都可笑:JDK 路径选到了 bin 目录、Tomcat 配置找不到入口、打开一个 JSP 文件发现语法全是灰的。这些坑我在过去几年里几乎每年都要重新踩一遍&#xff…

2026/9/22 6:28:10 阅读更多 →
重庆万州网站建设费用揭秘:3步搞定性能优化避坑指南

重庆万州网站建设费用揭秘:3步搞定性能优化避坑指南

重庆万州网站建设费用揭秘:3步搞定性能优化避坑指南 刚接手万州本地企业官网项目,最让新手头疼的往往不是代码,而是 域名服务器搞不懂 。很多老板问“重庆万州网站建设费用”到底多少,其实钱花在哪你心里没底,是因为你不懂背后的配置逻辑。别慌,域名解析、服务器带宽、SSL证书,这些看似复杂的术语,其实决定了…

2026/9/18 14:07:35 阅读更多 →

最新新闻

照着用就行:AI论文写作工具2026最新测评与推荐

照着用就行:AI论文写作工具2026最新测评与推荐

2026年真正好用的AI论文写作工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 …

2026/9/23 9:06:23 阅读更多 →
3 分钟画出第一张流程图:Mermaid 在线编辑器 mermaid-live-editor 新手实战手册

3 分钟画出第一张流程图:Mermaid 在线编辑器 mermaid-live-editor 新手实战手册

3 分钟画出第一张流程图:Mermaid 在线编辑器 mermaid-live-editor 新手实战手册 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/GitHub_Trendin…

2026/9/23 9:06:23 阅读更多 →
从“信任边界“视角看广电嵌入式终端安全缺陷挖掘思路

从“信任边界“视角看广电嵌入式终端安全缺陷挖掘思路

从"信任边界"视角,浅析广电嵌入式终端的安全缺陷挖掘思路阅读提示:本文对涉及的设备与系统均做脱敏处理——不出现厂商名称、产品型号、真实接口路径、账号凭据与网络拓扑。文中代码为示意性伪代码,非现场原文。所述缺陷已通过国家…

2026/9/23 9:06:23 阅读更多 →
3个步骤搞定药柜管理系统,源码解析带你避坑

3个步骤搞定药柜管理系统,源码解析带你避坑

3个步骤搞定药柜管理系统,源码解析带你避坑 刚学完 Python 或 Java 基础语法,代码能跑通,但一面对“药柜”这种具体业务需求就脑子发懵?别慌,这是从“写代码”到“做项目”的典型断层。很多人卡在不知道如何把零散的…

2026/9/23 9:06:23 阅读更多 →
搞定伟大的项目架构:3个步骤告别代码堆砌

搞定伟大的项目架构:3个步骤告别代码堆砌

搞定伟大的项目架构:3个步骤告别代码堆砌 学会语法却不知怎么搭项目,这是无数开发者卡脖子的真问题。刚跑通 Hello World,面对真实业务需求就懵了,代码写得像面条,改一处崩全身。别慌,这恰恰是从“写代码的人”到“做项目的人”的分水岭。…

2026/9/23 9:06:23 阅读更多 →
移居其一避坑指南:3个关键优化让项目跑飞

移居其一避坑指南:3个关键优化让项目跑飞

移居其一避坑指南:3个关键优化让项目跑飞 看了一堆教程还是不会写项目?别慌,这恰恰是大多数人的通病。理论都懂,代码一敲就错,项目一跑就卡。今天这篇避坑指南,不讲虚的,直接拿一个真实场景——“移居其一”数据处理——来拆解性能优化的全流程。…

2026/9/23 9:05:21 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →