【大話QT之十二】基於CTK Plugin Framework的外掛程式版本動態升級

來源:互聯網
上載者:User

標籤: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;    }}
這樣,在執行完上述所有的操作之後,當重新擷取外掛程式指標,調用介面實現功能時,就是最新外掛程式中實現的功能,這樣就實現了外掛程式的動態更新。

總結:

         這種基於外掛程式的開發方式處處提現出了優異之處,外掛程式更新這個功能點也是因應用的不同而有不同程度的需求。當時這裡有一點需要注意一下,如果外掛程式裡面實現了網路功能,這種情況下的更新可能會失敗,比如在新外掛程式中用於網路通訊的連接埠換掉了,就必須將原有外掛程式開啟的連接埠關閉掉,然後重新開啟,而這個過程中會發生什麼事情,就不是能控制的了的了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.