【大话QT之十二】基于CTK Plugin Framework的插件版本动态升级

应用需求:

某些场景下我们可能面临这样的问题,在运行着的应用程序不能终止的情况下,升级某个功能(或添,或减,或修改)。在不采用CTK Plugin Framework插件系统架构的情况下这将是很困难的,我们需要停止运行程序,然后在相关代码中作出修改,然后再重新编译,再重新启动我们的程序。而如果是基于CTK Plugin Framework插件系统架构构建的系统,则很容易的实现插件的动态升级。在【大话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;
    }
}

这样,在执行完上述所有的操作之后,当重新获取插件指针,调用接口实现功能时,就是最新插件中实现的功能,这样就实现了插件的动态更新。

总结:

这种基于插件的开发方式处处提现出了优异之处,插件更新这个功能点也是因应用的不同而有不同程度的需求。当时这里有一点需要注意一下,如果插件里面实现了网络功能,这种情况下的更新可能会失败,比如在新插件中用于网络通信的端口换掉了,就必须将原有插件打开的端口关闭掉,然后重新打开,而这个过程中会发生什么事情,就不是能控制的了的了。

【大话QT之十二】基于CTK Plugin Framework的插件版本动态升级

时间: 2024-10-30 22:57:06

【大话QT之十二】基于CTK Plugin Framework的插件版本动态升级的相关文章

【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级

应用需求: 某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减.或改动).在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的,我们须要停止执行程序,然后在相关代码中作出改动,然后再又一次编译.再又一次启动我们的程序. 而假设是基于CTK Plugin Framework插件系统架构构建的系统,则非常easy的实现插件的动态升级.在[大话Qt之四]ctkPlugin插件系统实现项目插件式开发中,我对ctkPlugin做了简

CTK框架——CTK Plugin Framework快速入门

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文件提供.

【大话QT之十五】ctkPluginFrameWork插件系统Windows下编译

使用ctkPluginFramework作为插件系统框架的确有着众多开发上的优势.最近收到一些站内信,大家都想使用ctkPluginFramework但是不知道如何编译,这篇教程就来讲一讲ctkPluginFramework插件系统在Windows下的编译过程. 准备条件: 1. 安装Git,我们通过它来下载CTK的源码. 2. 安装CMake,我们用它来生成vs下的sln解决方案文件. 相关站点: 1. CTK的官网:http://www.commontk.org/index.php/Main

【大话QT之十】实现FTP断点续传

应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传.下载)这一块现在既定了三种传输方式,即:Ftp传输.HTTP传输以及基于UDT的传输.且这三种数据传输方式是可配的,可以通过不同的接口调用.相比这三种方式,基于UDT的大量文件传输是比较值得研究与创新的地方,它在底层是基于UDP,在上层实现了可靠性的控制:同时它充分考虑到了基于在公网环境下基于Tcp进行传输时拥塞控制算法的缺点,实现了自己

javaweb学习总结(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册

一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp负责数据显示,javabean负责封装数据. Servlet+JSP+JavaBean模式程序各个模块之间层次清晰,web开发推荐采用此种模式. 这里以一个最常用的用户登录注册程序来讲解Servlet+JSP+JavaBean开发模式,通过这个用户登录注册程序综合案例,把之前的学过的XML.Xpat

【大话QT之十四】QT实现多语言切换

功能需求: 网盘客户端要能够实现多国语言的切换,第一版要支持中.英文的切换.在实现过程中感觉QT对多国语言的支持还是很不错的,制作多语言包很方便,切换的逻辑也很简单.下面就来看一下QT中如何制作多语言包. 实现方法: 为了支持国际化最关键的地方是制作多国语言包,然后再实现动态切换.QT里面既可以采用命令行也可以采用Qt Creator的界面操作来生成,这里我们利用Qt Creator来生成多国语言包.基本流程是,先生成ts文件,然后生成qm文件,最后通过QTranslator类来加载qm文件,实

大话设计模式第十二章---外观模式PHP实现

<?php class Sub_system_one { public function method_one() { echo "subsystem one method one<br/>"; } } class Sub_system_two { public function method_two() { echo "subsystem one method two<br/>"; } } class Sub_system_three

【大话QT之十三】系统软件自动部署实现方案

本篇文章是对[大话QT之十二]基于CTK Plugin Framework的插件版本动态升级文章的补充,在上篇文章中我们阐述的重点是新版本的插件已经下载到plugins目录后应该如何更新本地正在运行的程序,是整个插件升级实现的后半部分.本篇文章就来讲述软件自动部署(当然,也包括插件升级)的前半部分. 我们必须有这样一点认识,即:我们的程序是运行在用户机器上的,插件更新不是我们手动放进去的,而必须有一种机制使客户端能够检测到需要升级插件了,然后将插件自动下载下来,最后自动进行安装或插件更新. 系统

QT开发(六十二)———QT5解析Json文件

QT开发(六十二)---QT5解析Json文件 一.QT5 Json简介 QT4中使用第三方库QJson解析JSON文件. QT5新增加了处理JSON的类,类均以QJson开头,包含在QtCore模块中.QT5新增加六个相关类: QJsonArray 封装 JSON 数组 QJsonDocument 读写 JSON 文档 QJsonObject 封装 JSON 对象 QJsonObject::iterator 用于遍历QJsonObject的STL风格的非const遍历器 QJsonParseE