CTK 插件之间的依赖

前端之家收集整理的这篇文章主要介绍了CTK 插件之间的依赖前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

简述

插件依赖:是指一个插件在解析(或使用)时,不能脱离其他插件

插件是 CTK 中的基础元件,不同的插件之间可以相互依赖、引用,这样许多插件才会共同协作,实现一些比较复杂的功能

但是,在处理插件依赖的同时,也面临着一个极具挑战性的问题 - 如何保证每个插件都能加载成功?

版权所有:一去丶二三里,转载请注明出处:http://blog.csdn.net/liang19890820

@H_403_28@CTK 中的插件依赖

CTK 中的 MANIFEST.MF 文件 中,我们介绍过 CTK 本身提供了一些清单头。其中 Require-Plugin 用于标识插件所依赖的其他插件,头指令 resolution 用于标识 Require-Plugin 头中的解析类型,指令值包括

  • optional(弱依赖):表示所需的插件是可选的,并且即使所需的插件没有被解析,该插件也可以被解析。
  • mandatory(强依赖):表示在解析插件时,所需的插件也必须被解析。如果所需的插件不能被解析,则模块解析失败。
  • @H_403_23@

    假设,存在 A、B 两个插件,B 依赖 A(即:B 离不开 A)。那么,在插件加载过程中,可能会存在以下情况:

    • 弱依赖(optional

      • A、B 均存在,加载顺序:A -> B
      • A、B 均存在,加载顺序:B -> A
      • A 不存在,加载 B
      • @H_403_23@
      • 强依赖(mandatory

        • A、B 均存在,加载顺序:A -> B
        • A、B 均存在,加载顺序:B -> A
        • A 不存在,加载 B
        • @H_403_23@ @H_403_23@

          依赖验证

          为了测试上述情景,写一个简单的管理类来加载插件

          // 指定加载顺序
          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),这样才能确保主插件被加载成功。
          • @H_403_23@

            看似有这么一点意思:弱依赖,有没有你都无所谓;强依赖,必须有你,而且你还得比别人提前。

            异常的引发

            在使用 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),这时应该先加载哪个插件
            • @H_403_23@

              好不容易发现一条新路,又被堵死了。。。通过循环依赖,可以看出指定插件加载顺序貌似就是一个错误

              最终,通过源码可以发现,在执行 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;
              }

              这时,即使存在强依赖也不出现任何问题,这是因为所有的插件已经被优先安装了(包括它所依赖的插件)。

猜你在找的设计模式相关文章