[QPlugins] 如何编写基于 QPlugins 的插件程序

QPlugins 插件引擎目前正在开发过程中,目前已经可以编写基于 QPlugins 的插件了。QPlugins 中每个插件提供一到多种服务,这些服务被注册到全局的 PluginsManager 实例中,这个实例在所有的插件中都是可以直接访问的。

PluginsManager 将服务划分为三类:

1、加载器(Loaders 分支)

加载器用于加载插件里的服务,目前已经实现的加载器有:

  • DLL 加载器:名为TQDynamicLibraryLoader,仅适用于 Windows 平台;
  • BPL 加载器:名为TQPackageLoader,加载 Delphi 的BPL 插件,具体支持的平台范围取决于 Delphi / C ++ Builder 自身;
  • SO 加载器:名为TQShareObjectLoader,用于加载 Linux / FreeBSD 系操作系统的共享对象(Share Object,扩展名一般为 .so),但目前 Delphi 无法编译相应的插件,需要你用 GCC 或其它工具生成对应的 SO,放到相应的目录下加载;

目前待实现的加载器有:

  • 脚本加载器:名为TQPascalScriptLoader,暂时基于 JCL 的 JvIntercept 解释器,这个只是一个演示,实际上你可以替换为自己想用的任何解释器;Android / iOS / OSX 平台是否能够支持暂时我没有研究它的源码,暂不做承诺;
  • 进程加载器:名为TQProcessLoader,加载进程级别的插件。将会支持 Windows / Android / iOS / OSX 平台,但最先实现的肯定是 Windows 平台;
  • 网络加载器:名为TQTCPLoader,使用 TCP 协议加载远程计算机上的插件,将会支持 Windows / Android / iOS / OSX 平台;

2、路由器(Routers 分支)

目前未实现任何路由器,所以目前当你 ByPath 或 ById 访问相应的服务时,实际得到的就是该服务,如果不存在,那就返回空。将来会实现路由器插件;

3、其它服务(Services 分支)

服务分支下的服务是由各个插件提供的,其组织形式也由插件自身来决定。Services 分支下可以再加分支,形成一个树形结构;

好了,现在 QPlugins 大体包括的内容清楚了,我们说下一个基于 QPlugins 插件引擎的程序结构。

一个插件程序包括宿主程序和扩展插件两部分组成,宿主程序是一个可执行程序,可以运行在 Delphi 编译器支持的各种平台上。我们在使用插件之前,需要在宿主程序中做以下处理:

1、引用 QPlugins 单元和自己想用的加载器的实现单元;

2、如果需要使用外部插件提供的服务,那么在程序启动时,调用 PluginsManager.Loaders.Add 方法加入相应的加载器,注意这些加载器必需同时实现 IQService 和 IQLoader 接口;

3、调用 PluginsManager.Start 来加载插件来完成各项服务的注册;

4、使用 PluginsManager.ByPath 或 PluginsManager.ById 函数来获取到相应的服务实例,然后就可以利用服务提供的各项功能做实际的业务实现了。

程序退出时,会自动调用 PluginsManager.Stop 方法停止所有的服务,请不要在此后使用任何服务实例,以避免出现访问无效地址的错误。如果您缓存了某个服务的实例,请注册 NID_LOADERS_STOPPING 通知的响应,在其中释放相应的实例。

提供服务的插件可以是可执行程序,也可能是普通的文本(如脚本),它在加载器加载相应的文件时,负责将自身提供的服务注册到 PluginsManager 里,然后提供给其它的程序使用。具体的服务实现方式,对于服务管理器 PluginsManager 来说是透明的。以一个 DLL 插件为例,QPlugins 为大家提供了 QPlugins.VCL 单元,大家如果是编写基于 VCL 的 DLL 插件,请包含它,以保证能够正确的处理窗口消息。

插件程序的编写步骤包括:

1、编写服务接口实现;

2、通过加载器约定的方式提供服务注册和释放接口(如果有):

(1)、DLL、BPL 或 SO 的加载器没有提供任何额外的接口,用户直接使用开发语言内置的初始化和清理方式实现即可;

(2)、其它插件可能会约定相应的接口,具体请参与相关的加载器实现说明。

加载器在加载插件时,会调用相应的接口来完成服务的注册,在卸载插件时,会调用相应的接口来完成服务的释放。当然这个接口有可能是直接利用系统自带的接口,也有可能是加载器约定的接口,这个只有加载器和插件会关心,宿主应用不需要关心这些细节。

我们还需要知道以下几点:

1、服务的名称和 ID 在 QPlugins 是允许重复的,在没有实现路由器之前,它是始终取找到的第一个服务提供者;路由器实现之后,由路由器来规定怎么处理;

2、不仅宿主程序可以调用插件提供的服务,插件也可以调用宿主程序提供的服务。只需要通过 PluginsManager 查询到相应的实例就可以了。

3、注册服务时,请直接使用 Services 分支注册,而不要使用 PluginsManager 的 ByPath / ById 方法来获取服务的上级结点。 PluginsManager.ByPath / ById 是提供给调用者使用的,它可能会经过路由器过滤,从而映射到另一个实际路径上。

4、推荐注册服务时,放在自己的命名空间下,避免与其它人实现的插件命名冲突(仅是建议,没有强制规则),方便与其它人分享插件。

5、单实例和多实例的问题由服务自身来决定,如果要支持多实例,则重载 GetInstance 方法返回新的实例即可。默认实现下,返回的自身,也即单实例。

6、服务可能是线程安全的,也可能不是线程安全的,在使用相关服务前,请确认了解相关服务是否是线程安全的。

【注】 QPlugins 目前属于公筹项目,所以暂时不会开源,如需要请加入众筹群(QQ 群号:467381369)参与众筹。

分享到: