Qt学习--Qt Plugin创建及调用2(插件管理器)

转载 2018年04月16日 15:43:58

Qt Plugin创建及调用2–插件管理器

转载http://blog.csdn.net/liang19890820

简述

Qt 本身提供了插件相关的技术,但并没有提供一个通用的插件框架!倘若要开发一个较大的 GUI 应用程序,并希望使其可扩展,那么拥有这样一个插件框架无疑会带来很大的好处。

插件系统构成

插件系统,可以分为三部分:

  • 主系统
    通过插件管理器加载插件,并创建插件对象。一旦插件对象被创建,主系统就会获得相应的指针/引用,它可以像任何其他对象一样使用。
  • 插件管理器
    用于管理插件的生命周期,并将其暴露给主系统。它负责查找并加载插件,初始化它们,并且能够进行卸载。它还应该让主系统迭代加载的插件或注册的插件对象。
  • 插件
    插件本身应符合插件管理器协议,并提供符合主系统期望的对象。

实际上,很少能看到这样一个相对独立的分离,插件管理器通常与主系统紧密耦合,因为插件管理器需要最终提供(定制)某些类型的插件对象的实例。

程序流

框架的基本程序流,如下所示:

插件管理器

上面提到,插件管理器有一个职责 - 加载插件。那么,是不是所有的插件都需要加载呢?当然不是!只有符合我们约定的插件,才会被认为是标准的、有效的插件,外来插件一律认定为无效。

为了解决这个问题,可以为插件设定一个“标识(Interface)” - PluginInterface.h

#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H
#include <QStringList>
#include <QWidget>
class PluginInterface
{
public:
    virtual ~PluginInterface() {}

public:
    virtual void setInitData(QStringList &strlist) = 0;
    virtual void getResultData(QStringList &strlist) = 0;
};
#define PluginInterface_iid "QtPluginsTest.QtPluginsManager.PluginInterface"

Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)

#endif // PLUGININTERFACE_H

后期实现的所有插件,都必须继承自 PluginInterface,这样才会被认定是自己的插件,以防外部插件注入。

注意:使用 Q_DECLARE_INTERFACE 宏,将 PluginInterface接口与标识符一起公开。

插件的基本约束有了,插件的具体实现插件管理器并不关心,它所要做的工作是加载插件、卸载插件、检测插件的依赖、以及扫描插件的元数据(Json 文件中的内容)。。。为了便于操作,将其实现为一个单例。

qtpluginmanager.h内容如下:

#ifndef QTPLUGINSMANAGER_H
#define QTPLUGINSMANAGER_H

#include "qtpluginsmanager_global.h"
#include <QObject>
#include <QPluginLoader>
#include <QVariant>

class QtPluginsManagerPrivate;

class QTPLUGINSMANAGERSHARED_EXPORT QtPluginsManager : public QObject
{
    Q_OBJECT
public:
    QtPluginsManager();
    ~QtPluginsManager();


    static QtPluginsManager *instance();

    //加载所有插件
    void loadAllPlugins();
    //扫描JSON文件中的插件元数据
    void scanMetaData(const QString &filepath);
    //加载其中某个插件
    void loadPlugin(const QString &filepath);
    //卸载所有插件
    void unloadAllPlugins();
    //卸载某个插件
    void unloadPlugin(const QString &filepath);
    //获取所有插件
    QList<QPluginLoader *> allPlugins();
    //获取所有插件名称
    QList<QVariant> allPluginsName();
    //获取某个插件名称
    QVariant getPluginName(QPluginLoader *loader);
private:
    static QtPluginsManager *m_instance;
    QtPluginsManagerPrivate *d;
};

可以看到,插件管理器中有一个 d 指针,它包含了插件元数据的哈希表。此外,由于其拥有所有插件的元数据,所以还为其赋予了另外一个职能 - 检测插件的依赖关系:

class QtPluginsManagerPrivate
{
public:
    //插件依赖检测
    bool check(const QString &filepath);

    QHash<QString, QVariant> m_names; //插件路径--插件名称
    QHash<QString, QVariant> m_versions; //插件路径--插件版本
    QHash<QString, QVariantList>m_dependencies; //插件路径--插件额外依赖的其他插件
    QHash<QString, QPluginLoader *>m_loaders; //插件路径--QPluginLoader实例
};

注意: 这里的 check() 是一个递归调用,因为很有可能存在“插件A”依赖于“插件B”,而“插件B”又依赖于“插件C”的连续依赖情况。

QtPluginsManagerPrivate中的哈希表在初始化插件管理器时被填充:

void QtPluginsManager::loadAllPlugins()
{
    QDir pluginsdir = QDir(qApp->applicationDirPath());
    pluginsdir.cd("plugins");

    QFileInfoList pluginsInfo = pluginsdir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
    //初始化插件中的元数据
    for(QFileInfo fileinfo : pluginsInfo)
        scanMetaData(fileinfo.absoluteFilePath());

    //加载插件
    for(QFileInfo fileinfo : pluginsInfo)
        loadPlugin(fileinfo.absoluteFilePath());
}

元数据的具体扫描由 scan() 负责:

void QtPluginsManager::scanMetaData(const QString &filepath)
{
    //判断是否为库(后缀有效性)
    if(!QLibrary::isLibrary(filepath))
        return ;
    //获取元数据
    QPluginLoader *loader = new QPluginLoader(filepath);
    QJsonObject json = loader->metaData().value("MetaData").toObject();

    QVariant var = json.value("name").toVariant();
    d->m_names.insert(filepath, json.value("name").toVariant());
    d->m_versions.insert(filepath, json.value("version").toVariant());
    d->m_dependencies.insert(filepath, json.value("dependencies").toArray().toVariantList());

    delete loader;
    loader = nullptr;
}

一旦所有元数据被扫描,便可以检查是否能够加载插件:

void QtPluginsManager::loadPlugin(const QString &filepath)
{
    if(!QLibrary::isLibrary(filepath))
        return;

    //检测依赖
    if(!d->check(filepath))
        return;

    //加载插件
    QPluginLoader *loader = new QPluginLoader(filepath);
    if(loader->load())
    {
        PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());
        if(plugin)
        {
            d->m_loaders.insert(filepath, loader);
            plugin->connect_information(this, SLOT(onPluginInformation(QString&)), true);
        }
        else
        {
            delete loader;
            loader = nullptr;
        }
    }
}

注意: 这里用到了前面提到的标识 - PluginInterface,只有 qobject_cast 转换成功,才会加载到主系统中,这可以算作是真正意义上的第一道防线。

实际上,在内部检查是通过调用 QtPluginManagerPrivate::check() 递归地查询依赖元数据来完成的。

bool QtPluginsManagerPrivate::check(const QString &filepath)
{
    for(QVariant item : m_dependencies.value(filepath))
    {
        QVariantMap map = item.toMap();
        //依赖的插件名称、版本、路径
        QVariant name = map.value("name");
        QVariant version = map.value("version");
        QString path = m_names.key(name);

        /********** 检测插件是否依赖于其他插件 **********/
        // 先检测插件名称
        if(!m_names.values().contains(name))
        {
            QString strcons = "Missing dependency: "+ name.toString()+" for plugin "+path;
            qDebug()<<Q_FUNC_INFO<<strcons;
            QMessageBox::warning(nullptr, ("Plugins Loader Error"), strcons, QMessageBox::Ok);
            return false;
        }
        //再检测插件版本
        if(m_versions.value(path) != version)
        {
            QString strcons = "Version mismatch: " + name.toString() +" version "+m_versions.value(m_names.key(name)).toString()+
                                                    " but " + version.toString() + " required for plugin "+path;
            qDebug()<<Q_FUNC_INFO<<strcons;
            QMessageBox::warning(nullptr, "Plugins Loader Error", strcons, QMessageBox::Ok);
            return false;
        }
        //最后检测被依赖的插件是否还依赖其他的插件
        if(!check(path))
        {
            QString strcons = "Corrupted dependency: "+name.toString()+" for plugin "+path;
            qDebug()<<Q_FUNC_INFO<<strcons;
            QMessageBox::warning(nullptr, "Plugins Loader Error", strcons, QMessageBox::Ok);
            return false;
        }
    }

    return true;
}

插件卸载的过程正好相反:

void QtPluginsManager::unloadAllPlugins()
{
    for(QString filepath : d->m_loaders.keys())
        unloadPlugin(filepath);
}

而具体的卸载由 unloadPlugin() 来完成:

void QtPluginsManager::unloadPlugin(const QString &filepath)
{
    QPluginLoader *loader = d->m_loaders.value(filepath);
    //卸载插件,并从内部数据结构中移除
    if(loader->unload())
    {
        d->m_loaders.remove(filepath);
        delete loader;
        loader = nullptr;
    }
}

万事俱备,然后返回所有的插件,以便主系统访问:

QList<QPluginLoader *> QtPluginsManager::allPlugins()
{
    return d->m_loaders.values();
}

这样,整个插件管理的机制已经建立起来了,万里长征第一步。。。那剩下的事基本就比较简单了!插件的编写、插件之间的交互。。。

如何创建Qt Plugins (插件)之 使用高级api

Qt提供了2个api来创建插件:QStringList SimpleStylePlugin::keys() const { return QStringList() ...
  • penghuilater
  • penghuilater
  • 2016年11月17日 13:41
  • 1190

QT插件机制

Qt有两种与插件有关的API。一种用来扩展Qt本身的功能,如自定义数据库驱动,图像格式,文本编解码,自定义分格,等等,称为Higher-Level API 。另一种用于应用程序的功能扩展,称为Lowe...
  • imxiangzi
  • imxiangzi
  • 2016年04月10日 09:22
  • 4517

Qt一步一步实现插件调用(附源码)

最近手里几个项目都采用插件的方式进行开发工作,这里记录一下实现方法,给需要的同学一个参考, 在linux系统和window系统都能成功编译通过,不废话直接步骤 第一步:建立插件原型 ...
  • imxiangzi
  • imxiangzi
  • 2016年04月10日 08:58
  • 2554

QT之插件Plugin生成与使用

QT之插件Plugin生成与使用 QT之插件Plugin生成与使用 简述 插件生成 效果图 结尾简述 有时候,我们在做一个应用程序的时候,并不希望我们生成的软件仅仅是一个EXE文件,而是拆分成各个...
  • ly305750665
  • ly305750665
  • 2017年11月30日 20:37
  • 272

QT创建应用程序插件

创建插件要先创建接口,接口就是一个类,只包含纯虚函数,插件类要继承自该接口。插件类存储在一个共享库中,因此可以在应用程序运行时加载。...
  • jianggujin
  • jianggujin
  • 2017年05月04日 09:18
  • 364

Qt5的插件机制(7)--插件开发示例代码(Lower-level API)

插件代码 接口类头文件 MyPluginInterface.h #ifndef INTERFACES_H #define INTERFACES_H #include #define QtPl...
  • NewThinker_wei
  • NewThinker_wei
  • 2014年11月21日 02:39
  • 7996

如何创建Qt Plugins (插件)之 使用低级api

使用低级的api:扩展Qt的应用程序 不仅仅是Qt 本身,Qt的应用程序也可以通过plugins来扩展。这个就要求应用程序来探测和加载插件通过QPluginLoader.在这个前提下, 插件可以提供任...
  • penghuilater
  • penghuilater
  • 2016年11月18日 10:36
  • 1663

Qt Plugin创建及调用

概述插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现应用软件平台不具备的功能的程序。插件与宿主程序之间通过接口联系,就像硬件插卡一样,可以被随时删除,插入和修改,所以结构很灵活,容易...
  • yizhou2010
  • yizhou2010
  • 2017年10月17日 16:07
  • 137

ros 安装 qt creator plugin

步骤一:安装好ros,新建工作空间,在测试ros可以正常使用的情况下进行下一步; 步骤二:安装qt creator ros plugin,详情请见开发者连接:https://ros-industri...
  • nicai41
  • nicai41
  • 2017年12月22日 11:37
  • 162

【大话QT之四】ctkPlugin插件系统实现项目插件式开发

插件式开发体会:         自开始写【大话QT】系列就开始接触渲染客户端的开发,说是开发不如更多的说是维护以及重构,在接手这块的东西之前自己还有点犹豫,因为之前我一直认为客户端嘛,没什么技术含量...
  • houqingdong2012
  • houqingdong2012
  • 2014年04月16日 22:30
  • 4395
收藏助手
不良信息举报
您举报文章:Qt学习--Qt Plugin创建及调用2(插件管理器)
举报原因:
原因补充:

(最多只允许输入30个字)