第03课:入门篇——CTK Plugin Framework 基本原理

简述

CTK Plugin Framework 技术是面向 C++ 的动态模型系统。该系统允许插件之间的松散耦合,并且提供了设计良好的方式来进行功能和数据的交互。此外,它没有预先对插件施加限制,这样就可以很容易地将插件的相关部分嵌入到现有的工具包中。

体系架构

CTK Plugin Framework 设计(参考:设计文档 )受到了 OSGi(Java 的动态组件系统)的极大启发,并且它提供了一种能让应用程序(动态地)由许多不同的(可重用)组件组成的开发模型。该模型允许通过服务进行通信,服务是特定于组件之间的对象。

框架的分层模型,如下图所示:

image

  • Plugins(插件):由开发人员创建的 CTK 组件;
  • Services Layer(服务层):通过为 C++ 对象提供一个 publish-find-bind 模型,以动态方式连接插件;
  • Life Cycle Layer(生命周期层):用于安装、启动、停止、更新和卸载插件的 API;
  • Security(安全性):处理安全方面(目前尚不可用)。

插件

Plugin 是 CTK Plugin Framework 的核心,模块化特性在这一层得到了很好的实现。那么,究竟什么是 Plugin?

Plugin:是基于 C++/Qt 的一个共享库,并包含了资源文件和元数据(metadata)。

image

元数据的目的在于准确描述 Plugin 的特征,除了让 CTK Plugin Framework 对 Plugin 适当地进行各种处理(例如:依赖解析)之外,还能更好的对 Plugin 进行标识,以帮助用户对 Plugin 进行理解。

元数据被定义在 MANIFEST.MF 文件中,一个典型的 MANIFEST.MF 文件如下:

Plugin-SymbolicName: HelloCTK
Plugin-ActivationPolicy: eager
Plugin-Category: Demos
Plugin-ContactAddress: https://github.com/Waleon
Plugin-Description: A plugin for say hello
Plugin-Name: HelloCTK
Plugin-Vendor: Waleon
Plugin-Version: 1.0.0

虽然条目众多,但并非所有的都是必须的。这些元数据主要有这两部分:

  • Plugin 的标识符(必须):唯一标识一个 Plugin,由 Plugin-SymbolicName 表示。
  • 可读的信息(可选):帮助更好地理解和使用 Plugin,不对模块化特性产生任何的影响。

对于可选信息(例如:Plugin-Name、Plugin-Vendor 等),CTK Plugin Framework 甚至会无视这些内容。

服务层

服务可以看作是服务的提供者和使用者之间的一个契约,使用者一般不关心其实现的细节,只要满足这个契约(服务应该提供什么功能、满足什么格式)就好了。使用服务的过程也包含了发现服务和达成协议的形式,也就是说,需要通过服务的标志性特征来找到对应的服务。

一个插件可以创建一个对象,并在一个或多个接口(通常是一个只有纯虚方法的 C++ 类)下使用 CTK Service Registry 注册它。其他插件可以要求 registry 列出在特定接口下注册的所有服务(对象)。一个插件甚至可以等待一个特定的服务出现,然后收到回复。

因此,一个插件可以注册一个服务,也可以获得一个服务并侦听服务的出现或消失。任意数量的插件可以在相同的接口下注册服务,并且任意数量的插件都可以得到相同的服务,如下图(publish-find-bind 模型)所示:

image

如果多个插件在同一个接口下注册对象,则可以通过其属性进行区分。每个服务注册都有一套标准的自定义属性,可以使用过滤器来选择感兴趣的服务。属性也可以被用于应用程序级的其他角色。

发布服务

为了让其它 Plugin 能发现这个服务,必须用上下文对其进行注册,需要用到接口名、服务对象(接口的具体实现)和一个可选的 ctkDictionary 类型的属性信息:

ctkDictionary properties;
properties.insert("name", "Waleon");
properties.insert("age", 18);
ctkServiceRegistration registration = context->registerService<HelloService>(new HelloImpl(), properties);

这样,便可以得到一个 ctkServiceRegistration 对象,可以用这个对象来更新服务的属性:

registration.setProperties(newProperties);

也可以直接把这个服务移除:

registration.unregister();

注意: 这个 registration 对象不能和其他 Plugin 共享,因为它和发布服务的 Plugin 的生命周期相互依存。也就是说,如果这个 Plugin 已经不存在于框架执行环境中,那么这个对象也不应该存在了—— “皮之不存,毛将焉附”。

此外,如果在删除发布的服务之前 Plugin 停止了,框架会帮助你删除这些服务。

获取服务

一旦服务被发布,它将对其他 Plugin 可用。获取服务的方式非常简单,只需要提供一个接口名即可:

ctkServiceReference reference = context->getServiceReference<HelloService>();

注意: 这里的 reference 是服务对象的间接引用,可为什么要用间接引用而不直接返回实际的服务对象呢?

实际上,这是为了将服务的使用和服务的实现进行解耦。将服务注册表作为两者的中间人,不仅能够达到跟踪和控制服务的目的,同时还可以在服务消失以后通知使用者。

这个方法的返回类型是 ctkServiceReference,它可以在 Plugin 之间互享,因为它和使用服务的 Plugin 的生命周期无关。

生命周期层

生命周期层主要用于控制 Plugin 的安装、启动、停止、更新和卸载,它可以让我们从外部管理应用或者建立能够自我管理的应用(或将两者相结合),并且给了应用本身很大的动态性。

前面已经了解了 Plugin 的概念和作用,但要真正使用 Plugin,则需要使用生命周期层的 API 来和 CTK Plugin Framework 的生命周期层进行交互。

下图为 Plugin 生命周期的状态转换图:

image

生命周期层的 API 主要由三个核心部分组成:ctkPluginActivator、ctkPluginContext 和 ctkPlugin。

ctkPluginActivator

ctkPluginActivator:自定义 plugin 的启动和停止。

ctkPluginActivator 是一个接口,必须由框架中的每个插件实现。框架可以根据需要创建一个插件的 ctkPluginActivator 实例。如果一个实例的 ctkPluginActivator::start() 方法成功执行,则需要保证在插件停止时调用同一个实例的 ctkPluginActivator::stop() 方法。

ctkPluginContext

ctkPluginContext:一个 plugin 在框架内的执行上下文,该上下文用于授予对其他方法的访问,以便该插件可以与框架交互。

ctkPluginContext 提供的方法允许插件:

  • 订阅由框架发布的事件;
  • 使用 Framework Service Registry 注册服务对象;
  • 从 Framework Service Registry 检索 ServiceReferences;
  • 为引用的服务获取和发布服务对象;
  • 在框架中安装新的插件;
  • 获取框架中安装的插件列表;
  • 获得一个插件的 ctkPlugin 对象;
  • 为(由框架为插件提供的)持久存储区域中为文件创建 QFile 对象。

当使用 ctkPluginActivator::start() 方法启动时,将创建一个 ctkPluginContext 对象,并将其提供给与此上下文关联的插件。当使用 ctkPluginActivator::stop() 方法停止时,相同的 ctkPluginContext 对象将被传递给与此上下文关联的插件。ctkPluginContext 对象通常用于其关联插件的私有用途,并不意味着与插件环境中的其他插件共享。

与 ctkPluginContext 对象关联的 ctkPlugin 对象称为上下文插件。

ctkPluginContext 对象只有在它的上下文插件执行时才有效;也就是说,在上下文插件处于 STARTING、STOPPING、和 ACTIVE 状态的时段内。如果随后使用 ctkPluginContext 对象,则必须抛出一个 ctkIllegalStateException 异常。当上下文插件停止后,ctkPluginContext 对象不能被重用。

Framework 是唯一能够创建 ctkPluginContext 对象的实体,并且这些对象只在创建它们的 Framework 中有效。

ctkPlugin

ctkPlugin:Framework 中已安装的插件。

ctkPlugin 对象是定义一个已安装插件的生命周期的访问点,在插件环境中安装的每个插件都必须有一个相关的 ctkPlugin 对象。此外,插件必须有一个唯一的标识,在插件的生命周期中,这个标识不能改变(即使是在插件更新时),卸载和重新安装插件必须创建一个新的唯一标识。

插件有以下状态(状态是动态可变的,这些状态在特定条件下可以互相转换,见上图):

  • UNINSTALLED
  • INSTALLED
  • RESOLVED
  • STARTING
  • STOPPING
  • ACTIVE

要确定插件是否处于有效状态之一,可以使用 States 类型进行“或”运算。

插件只能在状态为 STARTING、ACTIVE 或 STOPPING 状态时执行代码。一个 UNINSTALLED 插件不能被设置为另一个状态,它是一个“僵尸”。

框架是唯一允许创建 ctkPlugin 对象的实体,并且这些对象仅在创建它们的框架内有效。

微信扫描登录
关注提示×
扫码关注公众号,获得课程更新动态!