(Qt) Qt项目的插件化

57 篇文章 7 订阅

一.前言<原创>

       笔者之前的项目所采用的是动态库的方式让程序运行时加载DLL,最近接触的几个项目均用插件化的方式加载程序所需要的模块。一开始我也纳闷,用我浅薄的理解Qt的插件化本质还是加载的dll,只不过是可以在运行期间进行dll的加载,无需加载lib,那么这样和存粹用动态库加载区别应该不大,一些动态库自带的好处比如解耦,灵活的好处,插件化也具备,那么这两个有什么不同呢,插件化是不是还能带来纯粹动态库没有的一些好处,下面就是我自己的角度想的好处。

好处:

       插件支持热插拔使应用程序的使用更加灵活,软件只需要用到的时候加载插件,不用的时候不加载,那么程序打包的时候就可以只打包需要用到的插件DLL,以我目前的项目举列子,项目中对各个格式进行了解析,所以会需要很多的库来支持解析能力,软件包的体量越来越大(差不多300M),支持插件了之后,比如软件点击了视频,发现没有这个解析视频的dll,后台联网下载,然后软件装配,这样之前300M的包剔除了很多不必要的DLL后目前只有最原始的100M,进行了软件包的一个瘦身。看的稍微本质一点,将软件本来运行前必须要加载的一个环节,变成了运行之后可以自由控制加载的一个行为,那么带来的灵活性和可操控性就会很强,项目的选择就会更多。本身就兼具传统动态库的所有优点,所以项目有时候更愿意做成插件化的形式。

二.插件化在Qt中的使用

1.插件库的封装

<1>添加插件库的接口头文件(暴露给外部模块)

定义一个用于外部访问插件的虚函数类,定义后用Q_DECLARE_INTERFACE将这两个外部需要访问的类和两个唯一字符串绑定,通知Qt元对象系统该接口。

#ifndef IMAINWIDGET_H
#define IMAINWIDGET_H

#include <QtGlobal>
#define MAINWIDGET_UUID "{89586123-e83d-451c-a1d5-84ff78b76d79}"

class IMainWidget
{
public:
    virtual QWidget *instance() = 0;
//添加外部模块需要访问的接口
};

class IMainWidgetPlugin
{
public:
    virtual QObject *instance() = 0;
    virtual IMainWidget *mainWidget() const = 0;
    virtual void showMainWidget() = 0;

protected:
    virtual void mainWidgetCreated() = 0;
    virtual void aboutToSignOut() = 0;
};
Q_DECLARE_INTERFACE(IMainWidget,"QtFrameworkTemplate.Plugin.IMainWidget/1.0")
Q_DECLARE_INTERFACE(IMainWidgetPlugin,"QtFrameworkTemplate.Plugin.IMainWidgetPlugin/1.0")
#endif

<2>添加插件库内部头文件(不暴露)

Q_PLUGIN_METADATA(IID IPlugin_iid FILE "mainwidget.json")这个宏第一次参数定义了一个uuid,保证唯一即可,第二个json是必须要有的,当无法找到指定的文件时,moc 会出现错误,即使是空的文件也行。

Q_INTERFACE 在另一篇博文中的说法是qobject_cast()能正确进行QObject*到接口指针的转换,所以这里加上了该类继承的两个类名Q_INTERFACES(IPlugin IMainWidgetPlugin)。

#ifndef MAINWIDGETPLUGIN_H
#define MAINWIDGETPLUGIN_H

#include "interfaces/ipluginmanager.h"
#include"interfaces/MainWidget/imainwidget.h"
#include"mainwidget.h"

class MainWidgetPlugin :
		public QObject,
		public IPlugin,
        public IMainWidgetPlugin
{
    Q_OBJECT
#if QT_VERSION >= 0x050000
    Q_PLUGIN_METADATA(IID IPlugin_iid FILE "mainwidget.json")
#endif
    Q_INTERFACES(IPlugin IMainWidgetPlugin)
public:
    MainWidgetPlugin();
    ~MainWidgetPlugin();
	virtual QObject* instance() { return this; }
	//IPlugin
    virtual QUuid pluginUuid() const { return MAINWIDGET_UUID; }
    virtual void pluginInfo(IPluginInfo *aPluginInfo);
    virtual bool initConnections(IPluginManager *aPluginManager, int &aInitOrder);
	virtual bool initObjects();
	virtual bool initSettings();
    virtual bool startPlugin();
    //IMainWidgetPlugin
    virtual IMainWidget *mainWidget() const;
    virtual void showMainWidget();
    virtual void showStartupDialog();
    virtual void closeStartupDialog();
    virtual bool isStartDialogVisible() const;

signals:
    void mainWidgetCreated();
    void aboutToSignOut();

protected slots:
    void onAboutToClose();
    void onMainWidgetAboutToDestory();

private:
    IPluginManager *m_pluginManager = NULL;
    MainWidget *m_mainWidget = NULL;
};

#endif // MAINWIDGETPLUGIN_H

这样一个插件库项目封装完成,编写业务逻辑生成出对应的dll即可。

2.插件库的加载

<1>插件模块读取

       这里贴了一个加载的函数,PluginManager类是专门用于管理加载的插件,可以看到,插件加载成功用插件们的通用基类IPlugin接受插件的实例对象,然后加工这个IPlugin信息存入自定义的PluginItem中,最后将这些插件PluginItem保存成QHash<QUuid, PluginItem> m_pluginItems中。

struct PluginItem
{
    IPlugin *plugin;
    IPluginInfo *info;
    QPluginLoader *loader;
    QTranslator *translator;
};

void PluginManager::loadPlugins()
{
    QDir pluginsDir(QApplication::applicationDirPath());
#if defined(Q_OS_MAC)
    if (pluginsDir.dirName() == "MacOS") {
        pluginsDir.cdUp();
        pluginsDir.cd("Resources");
    }
#endif
    if(pluginsDir.cd(kPluginDir))
    {
      QString s = pluginsDir.absolutePath();
        QString localeName = QLocale().name();
        QDir tsDir(translationsPath());

        loadCoreTranslations(tsDir,localeName);

        QStringList files = pluginsDir.entryList(QDir::Files);
        removePluginsInfo(files);

        foreach (QString file, files)
        {
            //qDebug() << pluginsDir.absoluteFilePath(file);
            QStringList error_module;
            if (QLibrary::isLibrary(file) && isPluginEnabled(file))
            {
                QPluginLoader *loader = new QPluginLoader(pluginsDir.absoluteFilePath(file),this);
                if (loader->load())
                {
                    IPlugin *plugin = qobject_cast<IPlugin *>(loader->instance());
                    if (plugin)
                    {
                        plugin->instance()->setParent(loader);
                        QUuid uid = plugin->pluginUuid();
                        if (!m_pluginItems.contains(uid))
                        {
                            PluginItem pluginItem;
                            pluginItem.plugin = plugin;
                            pluginItem.loader = loader;
                            pluginItem.info = new IPluginInfo;
                            pluginItem.translator =  NULL;

                            QTranslator *translator = new QTranslator(loader);
                            QString tsFile = file.mid(LIB_PREFIX_SIZE,file.lastIndexOf('.')-LIB_PREFIX_SIZE);
                            if (translator->load(tsFile,tsDir.absoluteFilePath(localeName)) || translator->load(tsFile,tsDir.absoluteFilePath(localeName.left(2))))
                            {
                                qApp->installTranslator(translator);
                                pluginItem.translator = translator;
                            }
                            else
                                delete translator;

                            plugin->pluginInfo(pluginItem.info);
                            savePluginInfo(file, pluginItem.info).setAttribute("uuid", uid.toString());

                            m_pluginItems.insert(uid,pluginItem);
                        }
                        else
                        {
                          error_module.append(file);
                          qInfo() << tr("Duplicate plugin uuid") << file << loader->errorString();
                            savePluginError(file, tr("Duplicate plugin uuid"));
                            delete loader;
                        }
                    }
                    else
                    {
                      error_module.append(file);
                      qInfo() << tr("Wrong plugin interface") << file << loader->errorString();
                        savePluginError(file, tr("Wrong plugin interface"));
                        delete loader;
                    }
                } else {
                  error_module.append(file);
                  qInfo() << tr("load error") << file << loader->errorString();
                  savePluginError(file, loader->errorString());
                  delete loader;
                  Q_ASSERT(false);
                }
            }

            if (!error_module.isEmpty()) {
              //BfEventTrack::GetInstance()->SendBSoftStartError(error_module);
              QString title = QString::fromUtf8("启动错误");
              QString message =
                  QString::fromUtf8("更新失败");

              QMessageBox::critical(nullptr, title, message);

              quit();
              return;
            }
        }

        QHash<QUuid,PluginItem>::const_iterator it = m_pluginItems.constBegin();
        while (it!=m_pluginItems.constEnd())
        {
            QUuid puid = it.key();
            if (!checkDependences(puid))
            {
                unloadPlugin(puid, tr("Dependences not found"));
                it = m_pluginItems.constBegin();
            }
            else if (!checkConflicts(puid))
            {
                foreach(QUuid uid, getConflicts(puid)) {
                    unloadPlugin(uid, tr("Conflict with plugin %1").arg(puid.toString())); }
                it = m_pluginItems.constBegin();
            }
            else
            {
                ++it;
            }
        }
    }
    else
    {
        qDebug() << tr("Plugins directory not found");
        quit();
    }
}

<2>插件模块使用

从之前我们存的QHash<QUuid, PluginItem> m_pluginItems 比对类名获取出需要的接口指针,进而进行插件接口的正常访问

  
//根据插件名获取单个插件
QList<IPlugin *> PluginManager::pluginInterface(const QString &AInterface) const
{
    //QList<IPlugin *> plugins;
    if (!m_plugins.contains(AInterface))
    {
        foreach(PluginItem pluginItem, m_pluginItems)
            if (AInterface.isEmpty() || pluginItem.plugin->instance()->inherits(AInterface.toLatin1().data()))
                m_plugins.insertMulti(AInterface,pluginItem.plugin);
    }
    return m_plugins.values(AInterface);
}




//获取到的IPlugin直接qobject_cast转化为需要用的实例
if (!m_mainWidgetPlugin) {
    IMainWidgetPlugin* m_mainWidgetPlugin = nullptr;
    IPlugin* plugin = NULL;
    if (m_pluginManager) {
      plugin = m_pluginManager->pluginInterface("IMainWidgetPlugin").value(0, NULL);
      if (plugin) {
      m_mainWidgetPlugin =qobject_cast<IMainWidgetPlugin*>(plugin->instance());

      //IMainWidgetPlugin都获取到了,就可以使用插件中的接口了
      //m_mainWidgetPlugin->接口
      }
    }
  }

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一款跨平台的C++应用程序开发框架,支持各种GUI(图形用户界面)和非GUI应用程序开发。Qt提供了插件(Plugin)机制,使开发者可以将应用程序的功能模块,以实现灵活的插件开发。 Qt插件机制允许开发者将应用程序的一部分功能独立封装成插件,并在运行时动态加载。这样的好处是插件可以在不重新编译整个应用程序的情况下进行更新或替换,极大地提升了开发效率和灵活性。 插件开发中,Qt提供了一些关键的类和接口,包括QPluginLoader、QObject和Q_EXPORT_PLUGIN2等。首先,使用QPluginLoader类可以在运行时加载插件,并提供了查找和实例插件的功能。其次,插件类需要派生自QObject类,并通过宏Q_OBJECT和Q_PLUGIN_METADATA来声明,以便Qt能够正确处理插件的元数据和信号槽机制。最后,Q_EXPORT_PLUGIN2宏用于导出插件类的实例,使其可以被QPluginLoader动态加载。 利用Qt插件机制,开发者可以将应用程序按功能划分为不同的插件模块,简开发过程和项目维护。插件可以通过简单的配置文件进行注册和管理,实现插件的自动加载和卸载。另外,Qt的信号槽机制可以在插件之间进行通信和交互。这使得多个独立开发的插件可以灵活地协同工作,提供更丰富的功能和扩展性。 总结来说,Qt插件机制使得应用程序的功能模块,提供了灵活的插件开发方式。开发者可以通过动态加载和卸载插件,实现插件的更新和替换,提升开发效率和项目的可维护性。插件之间可以通过信号槽机制进行通信和交互,实现更丰富的功能和扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值