【大話QT之十二】基於CTK Plugin Framework的插件版本號動態升級

應用需求:網絡

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

相關文章
相關標籤/搜索