应用需求:网络
某些场景下咱们可能面临这种问题,在执行着的应用程序不能终止的状况下,升级某个功能(或添,或减。或改动)。在不採用CTK Plugin Framework插件系统架构的状况下这将是很是困难的,咱们需要中止执行程序,而后在相关代码中做出改动,而后再又一次编译。再又一次启动咱们的程序。架构
而假设是基于CTK Plugin Framework插件系统架构构建的系统,则很是easy的实现插件的动态升级。在【大话Qt之四】ctkPlugin插件系统实现项目插件式开发中,我对ctkPlugin作了简介,在次就再也不反复。将主要精力放在。怎样解决插件的动态升级。函数
实现思路:this
ctkPlugin插件系统中,每个功能模块都是一个插件。而每个插件的开发都遵循必定的编写格式,当中:每个插件在定义时都会指定它的版本号信息,并生成其终于相应的dll插件(Linux下为.so插件)相应一个版本号信息,好比:com.lhtx.filetransfer_0.9.0.dll,并终于经过registerService注冊到插件系统中提供服务,经过getServiceReference和getService来从插件系统中获取插件实例。spa
那么,插件更新触发的机制是什么呢?一般在项目中,都会存在一个单独的plugins的文件夹,如下放置的是所有咱们需要使用到的插件。当系统启动时,会主动扫描该文件夹下的所有插件,并注冊到系统中。所以,插件更新触发的时机就是该文件夹下的文件发生变化,好比:本来plugins文件夹下存在一个com.lhtx.filetransfer_0.9.0.dll的插件,它的版本号信息是0.9.0。当咱们将一个com.lhtx.filetransfer_0.9.1.dll的插件放进去,它的版本号为0.9.1,就会触发版本号升级的事件。.net
要对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()的接口,更新本插件中所有使用到的插件。code
如下给出一个插件中的Upgrade接口的实现:blog
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关闭掉。而后又一次打开,而这个过程当中会发生什么事情。就不是能控制的了的了。