长期被 Delphi 开发者引以为傲、却让 Free Pascal 社区苦等十余年的动态包机制,近日出现了一次高调亮相。第三方分支维护方 GetLazarus 宣布,正式推出首个支持 Free Pascal Compiler(FPC)和 Lazarus IDE 的完整动态包(Dynamic Packages)实现。该功能宣称允许开发者在数秒内安装组件或 IDE 扩展,彻底告别每次调整组件都要全量静态重编整个 IDE 的笨拙历史,并支持构建导出原生 Pascal 类与类型的运行时插件。
然而细究代码源头与技术限制,这并非官方 upstream 主线的历史性里程碑,而是一次孤军深入的分支实践。
翻开代码仓库,并非官方主线合流
官方 FPC 基金会早于 2014 年就立项探讨动态包,由核心开发者 Sven Barth 牵头开发,但十年过去,官方任务板上该项目长期处于未完成状态。此次高调发布的版本,实际上完全托管于 GetLazarus 自行维护的 GitLab 分支(sysrpl/fpc 的 dynamic packages 分支),既未进入官方主线代码库,也没有经过官方核心团队的合流验收。

对于不知情的开发者而言,这很容易产生一种错觉,以为官方 Lazarus 终于补齐了对标 Delphi BPL(Borland Package Library)的最后一块拼图。但脱离官方主线的 Fork 往往意味着两难困境:开发者若选择这个版本,就必须将整个编译流水线绑定在第三方维护的特制工具链上;未来一旦官方主线发布重大更新或语言特性,该分支极易因维护成本过高而陷入停滞。
更值得关注的是跨平台支持的成色。虽然该分支宣称兼容 Linux、Windows 与 macOS,但文档明确指出,在 Apple Silicon(M1)架构上仍需大量编译器底层的适配工作,目前甚至仍在公开征集 M1 硬件以推进开发。
跨模块共享背后,普通动态库为何难以胜任
在桌面开发领域,普通共享库(Windows 下的 DLL、Linux 下的 .so)无法直接用作 Pascal 组件包,是困扰该生态多年的技术症结。

根据 Sven Barth 此前在邮件列表中的分析,当宿主程序和动态库各自独立编译时,即使引用了完全相同的 Exception 类,两者生成的虚方法表(VMT)指针也不相同。这意味着当动态库抛出异常时,宿主程序的 catch 块根本无法识别并捕获该异常类型。此外,跨模块传递字符串、动态数组等由 RTL 堆内存管理的复杂类型时,如果两端缺乏共享内存管理器与线程变量的协调机制,程序崩溃几乎不可避免。
GetLazarus 分支通过引入 DynPkgTypes 单元与编译器级元数据,确实在单一分支内强制打通了运行时类型信息(RTTI)、VMT 指针与动态数组管理。当用户修改组件源码后,IDE 无需再花费数十秒全量重编译,仅需一两秒更新对应动态包即可在托盘调出控件。
换取数秒热加载的代价,是放弃 Pascal 最具优势的紧凑单体二进制。
但这种便利并非没有工程代价。动态包会彻底破坏 Pascal 编译器引以为傲的 Smartlinking(死代码消除)。因为动态库必须向外部导出全部类与方法以供动态调用,编译器无法在链接阶段剔除未引用的冗余代码,导致拆分后的应用总磁盘占用显著大于单体编译生成的二进制。同时,动态包极度依赖包内附带的完整结构字典,执行常规的符号剥离(strip)会导致元数据丢失,直接使动态包失效。
- 风险.动态包要求所有包内的 Unit 名称必须全局唯一,若两个第三方库均包含名为 Widgets 的单元,系统将直接发生命名碰撞而无法同时载入。
热重载约束与架构选型建议
在实际开发工作流中,所谓的热插拔同样存在刚性边界。如果在设计器中重编译某个正在被打开窗体所使用的包,Lazarus 仍会强制要求开发者保存并关闭该窗体;在运行时卸载动态包前,也必须由开发者手动销毁所有属于该包的对象实例,否则将触发内存违规。

| 模块化方案 | 类型共享能力 | 体积开销 | 跨版本二进制稳定性 | 适用场景 |
|---|---|---|---|---|
| GetLazarus 动态包 | 原生支持 Pascal 类与 VMT | 显著膨胀(无死代码消除) | 脆弱(高度绑定特定 FPC 构建) | 内部原型与组件热开发 |
| Delphi BPL | 原生深度集成 | 适度增加 | 同一编译器版本内稳定 | 商业 Windows 桌面大型系统 |
| 标准 C ABI 动态库 | 需封装平坦接口或 COM | 紧凑(按需导出) | 极高(操作系统级稳定契约) | 长期维护的严肃插件架构 |
面对这套方案,正在选型的团队应当保持清醒。FPC 官方的预编译单元(PPU)格式和 RTL 依赖随小版本迭代频繁变动,缺乏跨版本的二进制稳定保障(ABI)。动态包在特定闭环内能大幅提升组件开发体验,但如果要在工业生产中构建生命周期长达数年的第三方插件系统,严格定义、跨语言的 C 语言导出接口依然是更安全的选择。
- 建议.将此分支视为测试组件或加速本地迭代的辅助工具,在核心生产发布中,继续坚守官方主线的静态单体编译流水线。
