如何为Delphi创建模块化插件

时间:2013-02-26 15:40:16

标签: delphi plugins architecture modular-design

使用Delphi 2010,我需要编写一个程序来支持模块或插件。虽然有点做作,但假设我有一个转换数据文件/文本文件的应用程序。它将支持30种输入格式和30种格式作为输出。第一个版本可能只实现其中的一些格式。我的挑战是我想要一个数据驱动的流程。

例如,假设我有一个PARSE_FILE例程。如果我的输入数据文件格式是'Format_A',那么当我调用PARSE_FILE时,它应该知道使用PARSE_FILE_Format_A,而不是PARSE_FILE例程的其他29个不同版本。

PARSE_FILE只是一个例子。我可能会有60个不同的常用函数,LOAD_FILE,GET_DELIMITER,PARSE_FILE等,但这些函数中的每一个对于30种不同格式中的每一种都会有所不同。我可以使用什么技术,以便在我使用FORMAT_A加载文件时,这60个不同的常用例程中的每一个都使用这60个例程的正确“版本”?

请记住,我只是从5种输入格式开始,稍后会添加其他格式,因此我需要一种集中定义此“映射”的方式,因此无论在何处使用这些例程我的代码,即使我调用泛型版本,也会使用正确的例程版本。

1 个答案:

答案 0 :(得分:5)

  1. 定义每个插件所需的标准例程集 模块在接口类型中实现。说,IFileFormatHandler, 其中包含PARSE_FILE函数等
  2. 遵循接口设计和职责分离的原则,避免将函数放在某些实现不感兴趣或无法实现的接口中。在单独的接口中定义可选功能,实现类可以选择实现与否。例如,如果您预计应用程序将会读取某些文件格式但由于各种原因无法写入,则应将读取操作放在一个接口中,并在不同的接口中编写操作。
  3. 如果您要编写Delphi中的所有插件,则应使用BPL包来共享常见类型。将您的IFileFormatHandler接口类型放在一个BPL包(即Common.bpl)中,以便所有模块都可以引用通用接口类型。每个插件模块本身也都在自己的BPL包中。 (多个文件格式处理程序可以存在于同一个BPL程序包中,但基线示例是每个BPL一个)
  4. 如果这是您构建插件架构的第一步,请不要尝试同时涵盖多语言支持。现在坚持使用Delphi。在Delphi中编写应用程序和模块。构建一个模块化项目,在Delphi中完成,然后退后一步,花一些时间了解COM或二进制接口需求,以支持用Delphi以外的语言编写的模块。
  5. 在您的常见BPL包中,还定义了一个全局函数,模块可以调用该函数使主机应用程序知道它们 - 例如RegisterPlugin(name: string; instance: IFileFormatHandler)。这会将插件名称和实例注册到内部列表中,主机应用程序可以使用该列表查找可用的插件并进行调用。
  6. 对于每个文件处理插件模块,定义一个实现共享BPL包中定义的公共接口的类。在类的单元初始化中,调用RegisterPlugion()函数以使用主机应用程序注册该类。
  7. 主机应用程序使用公共包,每个模块包使用公共包。
  8. 主机应用程序仅通过公共接口中定义的功能与模块实现交互。
  9. 主机应用程序可以使用IS来测试特定模块对象实例是否实现可选接口,使用AS来获取该可选接口。
  10. 接口是在Delphi中引用计数的,因此只要主机应用程序保持对模块对象实例的引用(例如,通过RegisterPlugin),模块实例将保持活动并保存在内存中。释放最后一个引用时,将丢弃模块实例。
  11. 主机应用程序需要在运行时使用LoadPackage或类似的fn查找并加载模块包bpls。
  12. 在整个应用程序中共享一个插件模块实例列表对大多数单线程应用程序都可以正常工作。如果您预计同时在多个线程中使用这些插件模块,请考虑将此设计转换为工厂模式,而不是在内存中保存模块的单例实例。通过使用工厂在使用线程内按需构造实例来管理多线程要比从多个线程调用一个必须安全的实例要容易得多且通常更高效。