【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级
应用需求:
某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减。或改动)。在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的,我们须要停止执行程序,然后在相关代码中作出改动,然后再又一次编译。再又一次启动我们的程序。
而假设是基于CTK Plugin Framework插件系统架构构建的系统,则非常easy的实现插件的动态升级。在【大话Qt之四】ctkPlugin插件系统实现项目插件式开发中,我对ctkPlugin做了简介,在次就不再反复。将主要精力放在。怎样解决插件的动态升级。
实现思路:
ctkPlugin插件系统中,每一个功能模块都是一个插件。而每一个插件的开发都遵循一定的编写格式,当中:每一个插件在定义时都会指定它的版本号信息,并生成其终于相应的dll插件(Linux下为.so插件)相应一个版本号信息,比如:com.lhtx.filetransfer_0.9.0.dll,并终于通过registerService注冊到插件系统中提供服务,通过getServiceReference和getService来从插件系统中获取插件实例。
那么,插件更新触发的机制是什么呢?通常在项目中,都会存在一个单独的plugins的文件夹,以下放置的是全部我们须要使用到的插件。当系统启动时,会主动扫描该文件夹下的全部插件,并注冊到系统中。因此,插件更新触发的时机就是该文件夹下的文件发生变化,比如:原本plugins文件夹下存在一个com.lhtx.filetransfer_0.9.0.dll的插件,它的版本号信息是0.9.0。当我们将一个com.lhtx.filetransfer_0.9.1.dll的插件放进去,它的版本号为0.9.1,就会触发版本号升级的事件。
要对plugins文件夹实现监控,使用QFileSystemWatcher全然能够满足我们的需求,仅仅须要通过以下的代码:
//! 对插件文件夹运行监控,为的是插件版本号升级时能够检測到新插件。从而实现插件热载入
m_pluginWatcher = new QFileSystemWatcher;
QString houqd = Parameters[LH_KEY_PLUGIN_PATH].toString();
m_pluginWatcher->addPath(Parameters[LH_KEY_PLUGIN_PATH].toString());
并通过 connect(m_pluginWatcher,SIGNAL(directoryChanged(QString)),this,SLOT(TriggerDirectoryChanged(QString))); 建立处理文件夹变化时的槽函数。
当检測到插件文件夹有更新时。接下来。我们就须要再一次遍历plugins文件夹,并将新填入的插件又一次注入到系统中,当下一次调用相同的插件接口中的函数时,ctkPlugin系统会自己主动调用版本号较高的插件接口中的函数。当plugins文件夹变化遍历插件时要注意,程序启动时已经注入到系统中的插件不能再次注冊。否则会出现错误。应该过滤掉,相关代码实现例如以下:
//! 文件夹被改变时被视为有新的插件进入。然后更新插件
void LHController::TriggerDirectoryChanged(const QString &strPath)
{
LoadAllPlugins(strPath, m_cnfDefaultConfig->value(LH_CONF_EXCD).toString()); if (m_bHasUpgrade)
{
QMapIterator<QString, QObject *> i(m_mapPlugins); while (i.hasNext())
{
i.next(); if (i.key().contains("com.lht.syncclient_0.9.0"))
{ qDebug() << "[Debug] I am plugin :: " << i.key(); //LHBaseInterface *Base = qobject_cast<LHBaseInterface *>(i.value());
LHBaseInterface *Base = qobject_cast<LHBaseAppInterface *>(i.value());
if (Base)
Base->Upgrade();
}
}
}
} void LHController::LoadAllPlugins(const QString &strPath, const QString &strFilter)
{
QString strFilter_1 = QString("*") + LIB_SUFFIX;
QString strExclude = strFilter;
if (!strExclude.isEmpty())
strExclude = "^((?!" + strExclude + ").)*$"; QDirIterator ditPlugin(strPath, QStringList(strFilter_1), QDir::Files); m_bHasUpgrade = false; qDebug()<<"==================================================================\r\nStart loading plugins ..."; while (ditPlugin.hasNext())
{
QString strPlugin = ditPlugin.next(); if (strPlugin.contains(QRegExp(strExclude, Qt::CaseInsensitive, QRegExp::RegExp)))
{
InstallPlugin(strPlugin);
}
} qDebug()<<m_strPluginLog;
qDebug()<<"Finish loading plugins!\r\n==================================================================";
} int LHController::InstallPlugin(const QString &strPlugin)
{
try
{
QString strPluginKey = GetPluginNamewithVersion(strPlugin); //! 检查是否已经载入, 这里在插件更新时会将老版本号插件过滤掉,不会反复载入老版插件两次
if (m_mapPlugins.contains(strPluginKey))
return LH_SUCCESS; //! 假设插件已经载入,则抛出ctkPluginException
QSharedPointer<ctkPlugin> Plugin = m_PluginFramework->getPluginContext()->installPlugin(QUrl::fromLocalFile(strPlugin));
Plugin->start(ctkPlugin::START_TRANSIENT); m_bHasUpgrade = true; m_strPluginLog += QObject::tr("%1 (%2) is loaded.\r\n").arg(Plugin->getSymbolicName()).arg(Plugin->getVersion().toString());
}
catch (const ctkPluginException &Exc)
{
m_strPluginLog += QObject::tr("Failed to load %1: ctkPluginException(%2).\r\n").arg(strPlugin).arg(Exc.what());
qDebug() << m_strPluginLog;
return LH_FAILURE;
}
catch (const std::exception &E)
{
m_strPluginLog += QObject::tr("Failed to load %1: std::exception(%2).\r\n").arg(strPlugin).arg(E.what());
qDebug() << m_strPluginLog;
return LH_FAILURE;
}
catch (...)
{
m_strPluginLog += QObject::tr("Failed to load %1: Unknown error.\r\n").arg(strPlugin);
qDebug() << m_strPluginLog;
return LH_UNKNOWN;
} return LH_SUCCESS;
}
到这里,新版本号的插件仅仅是载入到了我们的系统中,但插件系统中注冊的还是插件升级之前的引用。
我们必须提供一种更新机制,又一次获取一下对插件的引用才行。如今的实现思路是在每一个插件中提供一个Upgrade()的接口,更新本插件中全部使用到的插件。
以下给出一个插件中的Upgrade接口的实现:
void LHSyncClient::Upgrade()
{
Q_D(LHSyncClient);
QVariant varInstance; //! 測试又一次载入lht.com.upgradeone插件
ctkServiceReference refUpgradeTest = d->m_PluginContext->getServiceReference("LHUpgradeInterface");
d->m_UpgradeInterface = (qobject_cast<LHUpgradeInterface *>(d->m_PluginContext->getService(refUpgradeTest)));
if (!d->m_UpgradeInterface ||
(d->m_UpgradeInterface->Init(d->m_Parameters) != LH_SUCCESS) ||
(d->m_UpgradeInterface->CreateInstance(varInstance, d->m_Parameters) != LH_SUCCESS))
{
qDebug()<<QObject::tr("Module %1 is invalid").arg("com.lht.auth");
}
else
{
d->m_nUpgradeInterfaceInstance = varInstance.toInt();
} }
以上的代码就是又一次载入的LHUpgradeInterface插件。这里有一点须要注意:在m_mapPlugins中保存了全部插件的名称以及它实例的值,须要依据它来更新插件,而在又一次获取插件指针的地方:LHBaseInterface *Base = qobject_cast<LHBaseAppInterface
*>(i.value())这个地方。强转的类型必须是插件向系统注冊是提供的类型,假设不一致的话强转后的指针为NULL,比如:
void LHUpgradeOnePlugin::start(ctkPluginContext *Context)
{
m_Auth = new LHUpgradeOne();
Context->registerService(QStringList("<span style="color:#FF0000;">LHUpgradeInterface</span>"), m_Auth);
} void LHUpgradeOnePlugin::stop(ctkPluginContext *Context)
{
Q_UNUSED(Context)
if (m_Auth)
{
delete m_Auth;
m_Auth = 0;
}
}
这样,在运行完上述全部的操作之后,当又一次获取插件指针,调用接口实现功能时,就是最新插件中实现的功能,这样就实现了插件的动态更新。
总结:
这样的基于插件的开发方式处处提现出了优异之处,插件更新这个功能点也是因应用的不同而有不同程度的需求。当时这里有一点须要注意一下。假设插件里面实现了网络功能。这样的情况下的更新可能会失败,比方在新插件中用于网络通信的port换掉了。就必须将原有插件打开的port关闭掉。然后又一次打开,而这个过程中会发生什么事情。就不是能控制的了的了。
【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级的更多相关文章
- Bootstrap <基础三十二>模态框(Modal)插件
模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. 如果您想要单独引用该插件的功能,那么您需要引用 ...
- 【大话QT之十六】使用ctkPluginFramework插件系统构建项目实战
"使用ctkPluginFramework插件系统构建项目实战",这篇文章是写博客以来最纠结的一篇文章. 倒不是由于技术都多么困难,而是想去描写叙述一个项目架构採用ctkPlugi ...
- 【大话QT之十】实现FTP断点续传(需要设置ftp服务器为“PASV”被动接收方式)
应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传.下载)这一块现在既定了三种传输方式,即: ...
- 【大话QT之十】实现FTP断点续传
应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在client改动或新添加一个文件时.该文件要同步上传到server端相应的用户文件夹下,因此针对传输数据(即:上传.下载)这一块如今既定了三种 ...
- javaweb学习总结(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- javaweb(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- 【大话QT之十四】QT实现多语言切换
功能需求: 网盘client要可以实现多国语言的切换,第一版要支持中.英文的切换. 在实现过程中感觉QT对多国语言的支持还是非常不错的.制作多语言包非常方便.切换的逻辑也非常easy. 以下就来看一下 ...
- Java开发学习(十二)----基于注解开发依赖注入
Spring为了使用注解简化开发,并没有提供构造函数注入.setter注入对应的注解,只提供了自动装配的注解实现. 1.环境准备 首先准备环境: 创建一个Maven项目 pom.xml添加Spring ...
- 大话数据结构(十二)java程序——KMP算法及改进的KMP算法实现
1.朴素的模式匹配算法 朴素的模式匹配算法:就是对主串的每个字符作为子串开头,与要连接的字符串进行匹配.对主串做大循环,每个字符开头做T的长度的小循环,直到成功匹配或全部遍历完成为止. 又称BF算法 ...
随机推荐
- Shiro笔记(三)shiroFilter拦截器配置原则
参考: http://blog.csdn.net/yaowanpengliferay/article/details/17281341
- HDU 4463 Outlets 【最小生成树】
<题目链接> 题目大意: 给你一些点的坐标,要求你将这些点全部连起来,但是必须要包含某一条特殊的边,问你连起这些点的总最短距离是多少. 解题分析: 因为一定要包含那条边,我们就记录下那条边 ...
- ACM-ICPC 2018 南京赛区网络预赛 L 【分层图最短路】
<题目链接> 题目大意: 有N个城市,这些城市之间有M条有向边,每条边有权值,能够选择K条边 边权置为0,求1到N的最短距离. 解题分析: 分层图最短路模板题,将该图看成 K+1 层图,然 ...
- 不一样的go语言-error
前言 go语言的error处理方式,在目前流行的编程语言中属于刺头.似乎天生就是用来有别于他人标记.TIOBE排行榜全十除了C语言,无一例外是try catch的阵营.而排在go之前的语言除了C与 ...
- Linux使用nexus搭建maven私服
一.准备工作 系统:LINUX JDK:已安装(未安装详见jdk安装教程:http://www.cnblogs.com/muzi1994/p/5818099.html) ...
- groupadd 创建组
groupadd 创建组 1 注意 :root用户才有权使用这个命令 2 groupadd -g 744 cjh 指定组ID号 3 在/etc/passwd 产生一个 组ID GID gpasswd ...
- PHP 操作 MySQL 执行数据库事务
<?php $mysqli=new mysqli();//实例化mysqli $mysqli->connect('localhost','root','admin','test'); if ...
- SQL 并联更新
UPDATE o SET col2 = c.col3 FROM bo1 AS o JOIN bo2 AS c ON c.col3<>'' WHERE c.col3<>'' UP ...
- Kotlin基础(二)函数的定义与调用
函数的定义与调用 一.预备知识:在Kotlin中创建集合 fun main(args: Array<String>) { //Kotlin中定义各自集合 val ,,,) val list ...
- 最近在学习python,做了一道人机大战的题目,分享一下,虽然可能有些麻烦,但是每个人思维是不同的。
#题目如下:1:人和机器进行猜拳游戏写成一个类,首先选择角色:1 曹操 2张飞 3 刘备,然后选择的角色进行猜拳:1剪刀 2石头 3布 玩家输入一个1-3的数字 : 1 import random c ...