简述
插件是 CTK 中的基础元件,不同的插件之间可以相互依赖、引用,这样许多插件才会共同协作,实现一些比较复杂的功能。
但是,在处理插件依赖的同时,也面临着一个极具挑战性的问题 - 如何保证每个插件都能加载成功?
版权所有:一去丶二三里,转载请注明出处:http://blog.csdn.net/liang19890820
CTK 中的插件依赖
在 CTK 中的 MANIFEST.MF 文件 中,我们介绍过 CTK 本身提供了一些清单头。其中 Require-Plugin
用于标识插件所依赖的其他插件,头指令 resolution
用于标识 Require-Plugin
头中的解析类型,指令值包括:
optional
(弱依赖):表示所需的插件是可选的,并且即使所需的插件没有被解析,该插件也可以被解析。mandatory
(强依赖):表示在解析插件时,所需的插件也必须被解析。如果所需的插件不能被解析,则模块解析失败。
假设,存在 A、B 两个插件,B 依赖 A(即:B 离不开 A)。那么,在插件加载过程中,可能会存在以下情况:
弱依赖(
optional
)- A、B 均存在,加载顺序:A -> B
- A、B 均存在,加载顺序:B -> A
- A 不存在,加载 B
强依赖(
mandatory
)- A、B 均存在,加载顺序:A -> B
- A、B 均存在,加载顺序:B -> A
- A 不存在,加载 B
依赖验证
为了测试上述情景,写一个简单的管理类来加载插件:
// 指定加载顺序
bool Manager::loadAllPlugins(const QString &path)
{
QString strDll = path + "/plugin_a.dll";
bool isALoaded = loadPlugin(strDll);
strDll = path + "/plugin_b.dll";
bool isBLoaded = loadPlugin(strDll);
return (isALoaded && isBLoaded);
}
// 加载插件
bool Manager::loadPlugin(const QString &path)
{
ctkPluginContext* context = framework->getPluginContext();
try {
QUrl location = QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath());
QSharedPointer<ctkPlugin> plugin = context->installPlugin(location);
plugin->start(ctkPlugin::START_TRANSIENT);
} catch(const ctkPluginException &e) {
qDebug() << QString("Failed to install plugin") << e.what();
return false;
}
return true;
}
在测试时,注意调换插件 A、B 的加载顺序。除此之外,还需要修改插件 B 中的 MANIFEST.MF 文件(弱依赖用 optional
,强依赖用 mandatory
):
Require-Plugin: plugin.a; resolution:="mandatory"
经过反复测试,暂时可以得出以下结论:
- 使用
optional
:无论被依赖的插件(A)是否存在,也无论加载顺序如何,主插件(B)都可以加载成功。 - 使用
mandatory
:被依赖的插件(A)必须存在,且必须在主插件(B)加载之前被加载(顺序:A -> B),这样才能确保主插件被加载成功。
看似有这么一点意思:弱依赖,有没有你都无所谓;强依赖,必须有你,而且你还得比别人提前。
异常的引发
在使用 mandatory
时你会发现,倘若被依赖的插件(A)未加载或者不存在,则加载主插件(B)时会抛出以下异常:
ctkPluginException: Failed to resolve required plugin: plugin.a
这时因为在调用 ctkPlugin::start()
时,会执行依赖插件的检测:
void ctkPluginFrameworkContext::checkRequirePlugin(ctkPluginPrivate *plugin)
{
// ...
if (!ok && pr->resolution == ctkPluginConstants::RESOLUTION_MANDATORY)
{
tempResolved.clear();
if (debug.resolve)
{
qDebug() << "checkRequirePlugin: Failed to satisfy:" << pr->name;
}
throw ctkPluginException(QString("Failed to resolve required plugin: %1").arg(pr->name));
}
// ...
}
当所依赖的插件没有被解析时,就会抛出异常。
那么,倘若插件之间存在强依赖关系,又不想以硬编码的形式指定加载顺序怎么办?
解决依赖关系
看来,现在的所有问题都集中在一点上:如何保证插件的加载顺序?
若要自己实现一个加载顺序,就必须知道插件所依赖的其他插件。而这些信息可以由 MANIFEST.MF 文件获得,但是要读取这些信息又必须加载插件,这似乎陷入了一个死循环:
要定义加载顺序 -> 就要获取头信息 -> 就要读取 MANIFEST.MF 文件 -> 就要加载插件(存在依赖,又无法正常加载)
通过 CTK 文档,可以发现 MANIFEST.MF 文件的信息其实在安装插件后就可以获取到,也就是 ctkPluginContext::installPlugin()
之后。而异常是由 ctkPlugin::start()
引发的,那么何不将这两部分分开,先安装所有插件,再执行启动所有插件,这样就不会存在问题了。
// 加载所有插件
bool Manager::loadAllPlugins(const QString &path)
{
// 安装所有插件
QDirIterator itPlugin(path,QStringList() << "*.para",QDir::Files);
while (itPlugin.hasNext()) {
QString pluginPath = itPlugin.next();
if (!installPlugin(pluginPath))
return false;
}
// 自己实现一个加载规则(按顺序加载:A -> B -> C)
ctkPluginContext* context = framework->getPluginContext();
QList<QSharedPointer<ctkPlugin> > plugins = context->getPlugins();
foreach (QSharedPointer<ctkPlugin> plugin,plugins) {
QHash<QString,QString> headers = plugin->getHeaders();
// 获取 Require-Plugin 的信息
// 可以递归获取各个插件所依赖的插件,形成一条加载链
}
return true;
}
看似没太大问题,但再深入思考一下,存在以下几个问题:
- 解析头信息很容易出错
- 逻辑不太好写
- 若存在循环依赖(A -> B -> C -> A),这时应该先加载哪个插件?
好不容易发现一条新路,又被堵死了。。。通过循环依赖,可以看出指定插件加载顺序貌似就是一个错误。
最终,通过源码可以发现,在执行 CtkPlugin::start()
时,倘若所依赖的插件已经执行 ctkPluginContext::installPlugin()
了,那么也可以成功!所以,修改一下代码:
// 加载所有插件
bool Manager::loadAllPlugins(const QString &path)
{
// 安装所有插件
QDirIterator itPlugin(path,QStringList() << "*.dll",QDir::Files);
while (itPlugin.hasNext()) {
QString pluginPath = itPlugin.next();
if (!installPlugin(pluginPath))
return false;
}
// 启动所有插件
ctkPluginContext* context = framework->getPluginContext();
QList<QSharedPointer<ctkPlugin> > plugins = context->getPlugins();
foreach (QSharedPointer<ctkPlugin> plugin,plugins) {
if (!startPlugin(plugin))
return false;
}
return true;
}
// 安装插件
bool Manager::installPlugin(const QString &path)
{
ctkPluginContext* context = framework->getPluginContext();
try {
QUrl location = QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath());
QSharedPointer<ctkPlugin> plugin = context->installPlugin(location);
} catch(const ctkPluginException &e) {
qDebug() << QString("Failed to install plugin") << e.what();
return false;
}
return true;
}
// 启动插件
bool Manager::startPlugin(QSharedPointer<ctkPlugin> plugin)
{
try {
plugin->start(ctkPlugin::START_TRANSIENT);
} catch(const ctkPluginException &e) {
qDebug() << QString("Failed to start plugin") << e.what();
return false;
}
return true;
}