【大话QT之十二】基于CTK Plugin Framework的插件版本动态升级
2014-07-01 23:16
447 查看
应用需求:
某些场景下我们可能面临这样的问题,在运行着的应用程序不能终止的情况下,升级某个功能(或添,或减,或修改)。在不采用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;
}
}这样,在执行完上述所有的操作之后,当重新获取插件指针,调用接口实现功能时,就是最新插件中实现的功能,这样就实现了插件的动态更新。
总结:
这种基于插件的开发方式处处提现出了优异之处,插件更新这个功能点也是因应用的不同而有不同程度的需求。当时这里有一点需要注意一下,如果插件里面实现了网络功能,这种情况下的更新可能会失败,比如在新插件中用于网络通信的端口换掉了,就必须将原有插件打开的端口关闭掉,然后重新打开,而这个过程中会发生什么事情,就不是能控制的了的了。
某些场景下我们可能面临这样的问题,在运行着的应用程序不能终止的情况下,升级某个功能(或添,或减,或修改)。在不采用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;
}
}这样,在执行完上述所有的操作之后,当重新获取插件指针,调用接口实现功能时,就是最新插件中实现的功能,这样就实现了插件的动态更新。
总结:
这种基于插件的开发方式处处提现出了优异之处,插件更新这个功能点也是因应用的不同而有不同程度的需求。当时这里有一点需要注意一下,如果插件里面实现了网络功能,这种情况下的更新可能会失败,比如在新插件中用于网络通信的端口换掉了,就必须将原有插件打开的端口关闭掉,然后重新打开,而这个过程中会发生什么事情,就不是能控制的了的了。
相关文章推荐
- 【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级
- 【大话QT之十六】使用ctkPluginFramework插件系统构建项目实战
- 基于centos 6.2的eclipse-plugin插件各版本编译方法
- 【大话QT之十五】ctkPluginFrameWork插件系统Windows下编译
- 【大话QT之四】ctkPlugin插件系统实现项目插件式开发
- 【大话QT之十六】使用ctkPluginFramework插件系统构建项目实战
- 【大话QT之四】ctkPlugin插件系统实现项目插件式开发
- 基于jquery打造的百分比动态色彩条插件
- 基于jQuery的动态表格插件
- 动态加载js和css的jquery plugin (Jquery插件)[转]
- 基于 Silverlight的精简框架之版本升级及使用
- VS2012基于QT5.1自定接口及插件并实现动态加载
- RDIFramework.NET — 基于.NET的快速信息化系统开发框架- 5.4平台日志、异常管理、生成自动升级配置文件模块
- 基于插件技术的GIS应用框架(C# + ArcEngine9.3)(十二)
- 基于jQuery的插件扩展,主要用于识别浏览器内核与外壳的类型和版本
- ant编译hadoop+eclipse对应版本的eclipse hadoop plugin插件
- 基于PhoneGap2.0.0下ToastPlugin插件开发
- 基于ASP.NET Web Application的插件动态编译反射实现,附DEMO
- 欢迎使用VcPlugInHelper 插件 最新版本1.0.0.1728
- 插件代码升级到高版本时,如何处理一些在高版本中去不再提供的方法