CTK框架——CTK Plugin Framework快速入门
一、CTK Plugin Framework简介
1、CTK Plugin Framework简介
CTK Plugin Framework基于Qt Plugin System和Qt Service Framework实现,并且增加了以下特性来扩展:
A、插件元数据(由MANIFEST.MF文件提供);
B、一个定义良好的插件生命周期和上下文;
C、综合服务发现和注册;
在Qt Plugin System中,插件的元数据由JSON文件提供。
CTK Plugin Framework的核心架构主要包含两个组件:Plugin System和Service Registry。这两个组件是相互关联的,在API级别上的组合使得系统更加全面、灵活。
A、Plugin System
CTK Core依赖于QtCore模块,因此CTK Plugin Framework基于Qt Plugin System。Qt API允许在运行时加载和卸载插件,热插拔功能在CTK Plugin Framework中得到了加强,以支持透明化延迟加载和解决依赖关系。
插件的元数据被编译进插件内部,可以通过API进行提取。此外,插件系统还使用SQLite缓存了元数据,以避免应用程序加载时间问题。另外,Plugin System支持通过中央注册中心使用服务。
B、Service Registry
Qt Service Framework是Qt Mobility项目发布的一个Qt 解决方案,Qt服务框架允许“声明式服务”和按需加载服务实现。为了启用动态(非持久性)服务,Qt Mobility服务框架可以与Service Registry一起使用。
2、CTK Plugin Framework框架的优点
CTK Plugin Framework以OSGi规范为模型,并实现了几乎完整的OSGI框架API,因此使用CTK Plugin Framework开发基于Qt的C++应用程序有如下优点:
A、降低复杂性
使用CTK Plugin Framework进行应用开发只需进行插件开发,插件隐藏了内部实现,并通过定义良好的服务来和其它插件通信。隐藏内部机制意味着可以自由地更改实现,不仅有助于Bug数量的减少,还使得插件的开发变得更加简单,因为只需要实现已经定义好的一定数量的功能接口即可。
B、可复用
标准化的组件模型,在应用程序中使用第三方组件变得非常简单。
C、版本控制
在CTK Plugin Framework中,所有的插件都经过严格的版本控制,只有能够协作的插件才会被连接在一起。
D、动态更新
OSGi组件模型是一个动态模型,插件可以在不关闭整个系统的情况下被安装、启动、停止、更新和卸载。
E、自适应
OSGi组件模型是从头设计的,以允许组件的混合和匹配,要求必须指定组件的依赖关系,并且需要组件在其可选依赖性并不总是可用的环境中生存。Service Registry是一个动态注册表,其中插件可以注册、获取和监听服务。OSGI动态服务模型允许插件找出系统中可用的功能,并调整它们所能提供的功能,使得代码更加灵活, 并且能够更好地适应变化。
F、透明性
插件和服务是CTK插件环境中的一等公民。管理API提供了对插件的内部状态的访问,以及插件之间的连接方式。可以停止部分应用程序来调试某个问题,或者可以引入诊断插件。
G、开发简单
CTK插件相关的API非常简单,核心API不到25个类。核心API足以编写插件、安装、启动、停止、更新和卸载,并且还包含了所有的监听类。
CTK Plugin Framework不仅仅是组件的标准,还指定了如何安装和管理组件的API。API可以被插件用来提供一个管理代理,管理代理可以非常简单,如命令shell、图形桌面应用程序、Amazon EC2的云计算接口、或IBM Tivoli管理系统。标准化的管理API 使得在现有和未来的系统中集成CTK Plugin Framework变得非常容易。
H、懒加载
OSGi技术有很多的机制来保证只有当类真正需要的时候才开始加载插件。例如,插件可以用饿汉式启动,但是也可以被配置为仅当其它插件使用它们时才启动。服务可以被注册,但只有在使用时才创建。懒加载场景可以节省大量的运行时成本。
I、非独占性
CTK Plugin Framework不会接管整个应用程序,可以选择性地将所提供的功能暴露给应用程序的某些部分,或者甚至可以在同一个进程中运行该框架的多个实例。
J、非侵入
在一个CTK插件环境中,不同插件均有自己的环境。插件可以使用任何设施,框架对此并无限制。CTK服务没有特殊的接口需求,每个QObject都可以作为一个服务,每个类(包括非QObject)都可以作为一个接口。
二、CTK Plugin Framework架构
1、CTK Plugin Framework简介
CTK Plugin Framework设计参考了OSGi(Java的动态组件系统),并提供了一种能让应用程序(动态地)由许多不同的可重用组件组成的开发模型,允许通过服务进行通信。
OSGi规范的核心部分是一个框架,核心框架定义了应用程序的生命周期模式和服务注册。基于OSGI核心框架定义了大量的OSGi服务:日志、配置管理、HTTP(运行servlet)、XML分析、设备访问、软件包管理、许可管理、用户管理、IO连接、连线管理、Jini和UPnP。
CTK Plugin Framework框架的分层模型如下:
A、Plugins(插件):由开发人员创建的CTK组件;
B、Services Layer(服务层):通过为C++对象提供一个
publish-find-bind模型,以动态方式连接插件;
C、Life Cycle Layer(生命周期层):用于安装、启动、停止、更新和卸载插件的API;
D、Security(安全性):处理安全方面。
2、插件模块层
Plugin是CTK Plugin Framework的核心,是模块化特性的体现。
插件由插件激活器类Activator启动,激活器可以获取代表插件框架的插件上下文环境,插件上下文对象不能共享。
Plugin是基于C++/Qt的一个共享库,包含了资源文件和元数据(metadata)。
元数据的目的在于准确描述Plugin的特征,除了让CTK Plugin Framework对Plugin适当地进行各种处理(例如:依赖解析)外,还能更好的对Plugin进行标识,以帮助用户对Plugin进行理解。
元数据被定义在MANIFEST.MF文件中,典型的MANIFEST.MF文件如下:
Plugin-SymbolicName: HelloCTK
Plugin-ActivationPolicy: eager
Plugin-Category: demo
Plugin-ContactAddress: https://github.com/scorpiostudio
Plugin-Description: A plugin for say hello
Plugin-Name: HelloCTK
Plugin-Vendor: scorpio
Plugin-Version: 1.0.0
元数据主要分为两部分:
A、Plugin的标识符(必须):唯一标识一个 Plugin,由Plugin-SymbolicName表示。
B、可读信息(可选):帮助更好地理解和使用Plugin,不对模块化特性产生任何的影响。可选信息如Plugin-Name、Plugin-Vendor。
3、服务层
CTK插件框架提供了插件间通信的动态服务模型,一个激活的插件可以在任何时候注册(注销)0个或多个服务到框架。服务注册是一个具有可选注册属性的发布接口。通过接口和过滤表达式可以从插件框架获得服务引用。框架发布服务生命周期事件。
服务可以通过ctkPluginContext对象注册到插件框架。服务的注册和注销可以在任何时候进行。
服务是服务的提供者和使用者之间的一个契约,使用者一般不关心其实现的细节,只关心是否满足契约(服务应该提供什么功能、满足什么格式)。使用服务的过程包含了发现服务和达成协议的形式,即需要通过服务的标志性特征来找到对应的服务。
一个插件可以创建一个对象,并在一个或多个接口(通常是一个只有纯虚方法的C++类)下使用CTK Service Registry注册它。其它插件可以要求registry列出在特定接口下注册的所有服务(对象)。一个插件甚至可以等待一个特定的服务出现,然后收到回复。
因此,一个插件可以注册一个服务,也可以获得一个服务并侦听服务的出现或消失。任意数量的插件可以在相同的接口下注册服务,并且任意数量的插件都可以得到相同的服务。publish-find-bind模型如下:
如果多个插件在同一个接口下注册对象,则可以通过其属性进行区分。每个服务注册都有一套标准的自定义属性,可以使用过滤器来选择感兴趣的服务。属性也可以被用于应用程序级的其他角色。
A、发布服务
为了让其它Plugin能发现服务,必须用上下文对其进行注册,需要用到接口名、服务对象(接口的具体实现)和一个可选的ctkDictionary类型的属性信息:
ctkDictionary properties;
properties.insert("name", "scorpio");
properties.insert("age", 30);
ctkServiceRegistration registration = context->registerService<HelloService>(new HelloServiceImpl(), properties);
得到一个ctkServiceRegistration对象,用于更新服务的属性:
registration.setProperties(newProperties);
注销服务:
registration.unregister();
registration对象不能和其它Plugin共享,因为registration对象和发布服务的Plugin的生命周期相互依存。如果Plugin已经不存在于框架执行环境中,那么registration对象也不应该存在。
此外,如果在删除发布的服务前Plugin停止,框架会帮助删除这些服务。
B、获取服务
一旦服务被发布,服务将对其他Plugin可用。获取服务的方式非常简单,只需要提供一个接口名即可:
ctkServiceReference reference = context->getServiceReference<HelloService>();
reference对象是服务对象的间接引用。间接引用可以将服务的使用和服务的实现进行解耦。将服务注册表作为两者的中间人,不仅能够达到跟踪和控制服务的目的,同时还可以在服务消失以后通知使用者。
接口的返回类型是ctkServiceReference,可以在Plugin之间互享,因为reference对象和使用服务的Plugin的生命周期无关。
4、生命周期层
生命周期层主要用于控制Plugin的安装、启动、停止、更新和卸载,可以从外部管理应用或者建立能够自我管理的应用(或将两者相结合),并且给了应用本身很大的动态性。
Plugin的使用需要使用生命周期层的API来和CTK Plugin Framework的生命周期层进行交互。
Plugin生命周期的状态转换图:
生命周期层的API主要由三个核心部分组成:ctkPluginActivator、ctkPluginContext和ctkPlugin。
(1)、ctkPluginActivator
ctkPluginActivator:自定义plugin的启动和停止。
ctkPluginActivator是一个接口,必须由框架中的每个插件实现。插件必须提供一个由插件框架调用的插件激活器类。框架可以根据需要创建一个插件的ctkPluginActivator实例。如果一个实例的ctkPluginActivator::start()方法成功执行,则需要保证在插件停止时调用同一个实例的ctkPluginActivator::stop() 方法。
当插件进入ACTIVE状态时,框架会调用start方法,当插件离开ACTIVE状态时,插件框架会调用stop方法。每一个插件都会接收到一个访问插件框架的唯一ctkPluginContext对象。
(2)、ctkPluginContext
ctkPluginContext是一个plugin在框架内的执行上下文,用于授予对其它方法的访问,以便该插件可以与框架交互。
ctkPluginContext提供的方法允许插件:
A、订阅由框架发布的事件;
B、使用Framework Service Registry注册服务对象;
C、从Framework Service Registry检索ServiceReferences;
D、为引用的服务获取和发布服务对象;
E、在框架中安装新的插件;
F、获取框架中安装的插件列表;
G、获得一个插件的ctkPlugin对象;
H、为(由框架为插件提供的)持久存储区域中为文件创建QFile对象。
当使用ctkPluginActivator::start()方法启动时,将创建一个 ctkPluginContext对象,并将其提供给与此上下文关联的插件。当使用ctkPluginActivator::stop()方法停止时,相同的ctkPluginContext对象将被传递给与此上下文关联的插件。ctkPluginContext对象通常用于其关联插件的私有用途,并不意味着与插件环境中的其它插件共享。
与ctkPluginContext对象关联的ctkPlugin对象称为上下文插件。
ctkPluginContext对象只有在它的上下文插件执行时才有效;即在上下文插件处于STARTING、STOPPING和ACTIVE状态的时段内。如果随后使用ctkPluginContext对象,则必须抛出一个ctkIllegalStateException异常。当上下文插件停止后,ctkPluginContext对象不能被重用。
Framework是唯一能够创建ctkPluginContext对象的实体,并且ctkPluginContext对象只在创建它们的Framework中有效。
(3)、ctkPlugin
ctkPlugin是Framework中已安装的插件。
ctkPlugin对象是定义一个已安装插件的生命周期的访问点,在插件环境中安装的每个插件都必须有一个相关的ctkPlugin对象。此外,插件必须有一个唯一的标识,在插件的生命周期中,唯一标识不能改变(即使是在插件更新时),卸载和重新安装插件必须创建一个新的唯一标识。
插件有以下状态(状态是动态可变的,在特定条件下可以互相转换):
UNINSTALLED
INSTALLED
RESOLVED
STARTING
STOPPING
ACTIVE
要确定插件是否处于有效状态之一,可以使用States类型进行“或”运算。
插件只能在状态为STARTING、ACTIVE或STOPPING状态时执行代码。一个UNINSTALLED插件是一个僵尸,不能被设置为另一个状态。
框架是唯一允许创建ctkPlugin对象的实体,并且ctkPlugin对象仅在创建它们的框架内有效。
三、CTK Plugin Framework加载插件
1、CTK Plugin Framework使用流程
CTK插件框架的使用流程如下:
A、初始化并启动插件框架:由ctkPluginFramework::init() 和ctkPluginFramework::start()完成;
B、获取上下文:由ctkPluginFramework::getPluginContext()完成
C、安装插件:由ctkPluginContext::installPlugin()完成,返回一个ctkPlugin对象。
D、启动插件:由ctkPlugin::start()完成。
E、获取服务引用:由ctkPluginContext::getServiceReference()完成。
F、获取指定ctkServiceReference引用的服务对象:由ctkPluginContext::getService()完成。
G、调用服务。
2、创建CTK插件
本节创建一个简单的插件。
接口定义HelloService.h如下:
#ifndef HELLOSERVICE_H
#define HELLOSERVICE_H
#include <QtPlugin>
class HelloService
{
public:
virtual ~HelloService() {}
virtual void printHello() = 0;
};
#define HelloService_iid "org.commontk.service.demo.HelloService"
Q_DECLARE_INTERFACE(HelloService, HelloService_iid)
#endif // HELLOSERVICE_H
接口实现:
HelloServiceImpl.h文件:
#ifndef HELLOSERVICEIMPL_H
#define HELLOSERVICEIMPL_H
#include <QObject>
#include "HelloService.h"
#include <ctkPluginContext.h>
class HelloServiceImpl : public QObject, public HelloService
{
Q_OBJECT
Q_INTERFACES(HelloService)
public:
HelloServiceImpl(ctkPluginContext* context);
void printHello();
};
#endif // HELLOSERVICEIMPL_H
HelloServiceImpl.cpp文件:
#include "HelloServiceImpl.h"
#include <ctkPluginContext.h>
#include <QtDebug>
HelloServiceImpl::HelloServiceImpl(ctkPluginContext *context)
{
context->registerService<HelloService>(this);
}
void HelloServiceImpl::printHello()
{
qDebug() << "Hello,CTK Plugin!";
}
插件实现:
HelloCTKPlugin.h文件:
#ifndef HELLOCTKPLUGIN_H
#define HELLOCTKPLUGIN_H
#include <QObject>
#include <QtPlugin>
#include <ctkPluginActivator.h>
#include <QScopedPointer>
#include "HelloService.h"
class HelloCTKPlugin : public QObject, public ctkPluginActivator
{
Q_OBJECT
Q_INTERFACES(ctkPluginActivator)
//Qt5版本
#if(QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
Q_PLUGIN_METADATA(IID "HelloCTKPlugin")
#endif
public:
void start(ctkPluginContext* context);
void stop(ctkPluginContext* context);
private:
QScopedPointer<HelloService> m_service;
};
#endif // HELLOCTKPLUGIN_H
HelloCTKPlugin.cpp文件:
#include "HelloCTKPlugin.h"
#include "HelloServiceImpl.h"
void HelloCTKPlugin::start(ctkPluginContext *context)
{
m_service.reset(new HelloServiceImpl(context));
}
void HelloCTKPlugin::stop(ctkPluginContext *context)
{
Q_UNUSED(context)
}
//Qt4版本
#if(QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
Q_EXPORT_PLUGIN2(HelloCTK, HelloCTKPlugin)
#endif
插件元数据文件MANIFEST.MF:
Plugin-SymbolicName: HelloCTKPlugin
Plugin-ActivationPolicy: eager
Plugin-Category: demo
Plugin-ContactAddress: https://github.com/scorpiostudio
Plugin-Description: A plugin for print hello
Plugin-Name: HelloCTKPlugin
Plugin-Vendor: scorpio
Plugin-Version: 1.0.0
QRC资源文件resource.qrc:
<RCC>
<qresource prefix="/HelloCTKPlugin/META-INF">
<file>MANIFEST.MF</file>
</qresource>
</RCC>
插件工程文件HelloCTK.pro:
QT += core
QT -= gui
TARGET = HelloCTKPlugin
CONFIG += plugin
TEMPLATE = lib
# CTK 安装路径
CTK_INSTALL_PATH = /usr/local/CTK
# CTK插件相关库所在路径(CTKCore.lib、CTKPluginFramework.lib)
CTK_LIB_PATH = $$CTK_INSTALL_PATH/lib/ctk-0.1
# CTK插件相关头文件所在路径(ctkPluginFramework.h)
CTK_INCLUDE_PATH = $$CTK_INSTALL_PATH/include/ctk-0.1
LIBS += -L$$CTK_LIB_PATH -lCTKCore -lCTKPluginFramework
INCLUDEPATH += $$CTK_INCLUDE_PATH
HEADERS += HelloService.h HelloServiceImpl.h HelloCTKPlugin.h
SOURCES += HelloServiceImpl.cpp HelloCTKPlugin.cpp
RESOURCES += Resource.qrc
工程树如下:
3、CTK插件框架使用实例
本例以HelloCTKPlugin插件为例,演示如何使用CTK Plugin Framework来加载插件并获取特定的服务。
工程文件如下:
QT += core
QT -= gui
TARGET = test
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
#CTK安装路径
CTK_INSTALL_PATH = /usr/local/CTK
#CTK插件相关库所在路径(CTKCore.lib、CTKPluginFramework.lib)
CTK_LIB_PATH = $$CTK_INSTALL_PATH/lib/ctk-0.1
#CTK插件相关头文件所在路径(ctkPluginFramework.h)
CTK_INCLUDE_PATH = $$CTK_INSTALL_PATH/include/ctk-0.1
LIBS += -L$$CTK_LIB_PATH -lCTKCore -lCTKPluginFramework
INCLUDEPATH += $$CTK_INCLUDE_PATH
SOURCES += main.cpp
main.cpp文件:
#include <QCoreApplication>
#include <QDirIterator>
#include <QtDebug>
#include <ctkPluginFrameworkFactory.h>
#include <ctkPluginFramework.h>
#include <ctkPluginException.h>
#include <ctkPluginContext.h>
#include "../HelloCTK/HelloService.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ctkPluginFrameworkFactory frameWorkFactory;
QSharedPointer<ctkPluginFramework> framework = frameWorkFactory.getFramework();
try
{
// 初始化并启动插件框架
framework->init();
framework->start();
qDebug() << "CTK Plugin Framework start ...";
}
catch (const ctkPluginException &e)
{
qDebug() << "Failed to initialize the plugin framework: " << e.what();
return -1;
}
// 获取插件上下文
ctkPluginContext* context = framework->getPluginContext();
// 获取插件所在位置
QString path = QCoreApplication::applicationDirPath() + "/plugins";
// 遍历路径下的所有插件
QDirIterator itPlugin(path, QStringList() << "*.dll" << "*.so", QDir::Files);
while (itPlugin.hasNext())
{
QString strPlugin = itPlugin.next();
try
{
// 安装插件
QSharedPointer<ctkPlugin> plugin = context->installPlugin(QUrl::fromLocalFile(strPlugin));
// 启动插件
plugin->start(ctkPlugin::START_TRANSIENT);
qDebug() << QString("Plugin %1 start ...").arg(
plugin.data()->getSymbolicName());
}
catch (const ctkPluginException &e)
{
qDebug() << "Failed to install plugin" << e.what();
return -1;
}
}
// 获取服务引用
ctkServiceReference reference = context->getServiceReference<HelloService>();
if (reference)
{
// 获取指定 ctkServiceReference 引用的服务对象
HelloService* service = qobject_cast<HelloService *>(
context->getService(reference));
if (service != NULL)
{
// 调用服务
service->printHello();
}
}
return app.exec();
}
ctkPluginContext是框架内一个插件的执行上下文,ctkPluginContext用于授予对其它方法的访问权,以便插件可以与框架进行交互。使用ctkPluginContext对象可以安装新的插件,并获得发布服务对象。
在运行程序前,需要在构建目录中(或可执行程序同级目录)创建一个plugins目录,用于专门存放插件。
将HelloCTKPlugin插件libHelloCTKPlugin.so放到plugins目录下,执行结果如下:
HelloCTKPlugin插件已经可以对外提供服务。
原文地址:http://blog.51cto.com/9291927/2122371