当前位置:网站首页>(Qt) Qt项目的插件化
(Qt) Qt项目的插件化
2022-07-16 04:40:00 【苏州-青蛙】
一.前言<原创>
笔者之前的项目所采用的是动态库的方式让程序运行时加载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->接口
}
}
}
边栏推荐
- Three lines (spring daily question 59)
- [target tracking] image inter frame difference target detection based on background subtraction and MATLAB simulation
- 腾讯员工发帖找对象,表示偏爱程序员!评论火了......丨黑马头条
- marginalization
- Function overloading
- How to optimize the performance of canvas?
- Common and practical SQL statements
- Abbyy finereader 15 standard OCR character recognition and PDF editing software tool
- [step on the pit] resurrect Pico go
- Marginalization
猜你喜欢

关于XML文件(五)

Binary tree, traversal

Introduction to T100 interface development steps
![Guess the size of the number ii[what problem does dynamic planning solve?]](/img/a0/6f94899557df7aff18377411cc10a8.png)
Guess the size of the number ii[what problem does dynamic planning solve?]

Test / develop programmers' humorous "self mockery"? Impression genre

文旅夜游项目助力夜间经济发展

三极管的基础知识(下)②

Collection和Collections区别

Differences between collections and collections

Enterprise OA system based on SSM, high-quality paper examples, attached source code, database script, project introduction and operation tutorial, Paper Writing Tutorial
随机推荐
基于JSP+Servlet的高校疫情防控系统
【FPGA教程案例25】通过NCO核和除法器实现tan(x)计算
Reading true questions | reading true questions record 2
Flutter ListView controller. Animateto is invalid
T100debug操作记录
Les employés de Tencent postent pour trouver des objets, ce qui indique une préférence pour les programmeurs! Les commentaires sont en feu... 丨 Black Horse Headlines
数字孪生工厂丨智慧工厂孪生驾驶舱,实现智能化精益生产管理
Golang---------小试牛刀 gin框架文件上传
Hcip the next day
XML file delete comments
ubuntu 18.04 使用 tar包安装mysql5.7.35
Marginalization
The version of NPM does not match that of node. When the NPM result is updated, an error is reported. How can the previous NPM version be returned? Or how to check the NPM version of node adaptation
The difference between function and method in rust
Ubuntu 18.04 install mysql5.7.35 with tar package
What points should be paid attention to in the selection of project management system?
[step on the pit] resurrect Pico go
阅读真题 | 真题阅读 做题记录 二
JS how to realize the automatic scrolling and looping of the list
How does golang calculate constellations and zodiac signs based on birthdays