標籤:style blog http color 使用 檔案
應用需求:
某些情境下我們可能面臨這樣的問題,在運行著的應用程式不能終止的情況下,升級某個功能(或添,或減,或修改)。在不採用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; }}這樣,在執行完上述所有的操作之後,當重新擷取外掛程式指標,調用介面實現功能時,就是最新外掛程式中實現的功能,這樣就實現了外掛程式的動態更新。
總結:
這種基於外掛程式的開發方式處處提現出了優異之處,外掛程式更新這個功能點也是因應用的不同而有不同程度的需求。當時這裡有一點需要注意一下,如果外掛程式裡面實現了網路功能,這種情況下的更新可能會失敗,比如在新外掛程式中用於網路通訊的連接埠換掉了,就必須將原有外掛程式開啟的連接埠關閉掉,然後重新開啟,而這個過程中會發生什麼事情,就不是能控制的了的了。