應用需求:網絡
某些場景下咱們可能面臨這種問題,在執行着的應用程序不能終止的狀況下,升級某個功能(或添,或減。或改動)。在不採用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關閉掉。而後又一次打開,而這個過程當中會發生什麼事情。就不是能控制的了的了。