zoukankan      html  css  js  c++  java
  • 【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级

    应用需求:

            某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减。或改动)。在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的,我们须要停止执行程序,然后在相关代码中作出改动,然后再又一次编译。再又一次启动我们的程序。

    而假设是基于CTK Plugin Framework插件系统架构构建的系统,则非常easy的实现插件的动态升级。在【大话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()<<"==================================================================
    Start 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!
    ==================================================================";
    }
    
    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.
    ").arg(Plugin->getSymbolicName()).arg(Plugin->getVersion().toString());
        }
        catch (const ctkPluginException &Exc)
        {
            m_strPluginLog += QObject::tr("Failed to load %1: ctkPluginException(%2).
    ").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).
    ").arg(strPlugin).arg(E.what());
    		qDebug() << m_strPluginLog;
            return LH_FAILURE;
        }
        catch (...)
        {
            m_strPluginLog += QObject::tr("Failed to load %1: Unknown error.
    ").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;
        }
    }
    这样,在运行完上述全部的操作之后,当又一次获取插件指针,调用接口实现功能时,就是最新插件中实现的功能,这样就实现了插件的动态更新。

    总结:

             这样的基于插件的开发方式处处提现出了优异之处,插件更新这个功能点也是因应用的不同而有不同程度的需求。当时这里有一点须要注意一下。假设插件里面实现了网络功能。这样的情况下的更新可能会失败,比方在新插件中用于网络通信的port换掉了。就必须将原有插件打开的port关闭掉。然后又一次打开,而这个过程中会发生什么事情。就不是能控制的了的了。

  • 相关阅读:
    Nginx 静态资源缓存设置
    Ubuntu Linux 与 Windows 7双系统安装教程(图文)
    配置可以通过http协议访问的svn服务器
    CentOS下搭建SVN服务器
    LINUX服务器下用root登录ftp
    CentOS 6下编译安装MySQL 5.6
    Jenkins代码管理
    python学习:备份文档并压缩为zip格式
    centos 7 双网卡建网桥脚本实现
    python学习:使用正则收集ip信息
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/6684479.html
Copyright © 2011-2022 走看看