插件管理系统实现

news/2024/9/22 19:10:44/

前言

上文写了插件管理系统的设计思路,这一篇文章主要介绍插件的实现。主要从三个方面介绍实现;

  1. 插件加载的实现
  2. 插件管理的实现;
  3. 插件dll 的实现;

文中不会完整的介绍实现过程,仅介绍重要和实现过程中要注意的事项;

插件加载的实现

插件的加载显示信息,决定了插件要实现哪些接口(后面章节中的“插件dll 的实现”),例如要显示插件的名称、版本号、插件类型、插件描述,这些都是插件的基本信息。

插件的加载显示在QTreeWidget 或QListWidget中

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);m_pluginManager = PluginManager::getPluginManager(this);m_pluginManager->loadPlugin();QVector<PluginBase*>  plugins = m_pluginManager->allLoadPlugins();for(int i=0;i<plugins.size();i++){QListWidgetItem *item = new QListWidgetItem();QVariant variant = QVariant::fromValue(reinterpret_cast<quintptr>(plugins[i]));item->setData(Qt::UserRole,variant );item->setText(plugins[i]->pluginName());ui->listWidget->addItem(item);connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem *item)),this,SLOT(itemFunc(QListWidgetItem *item)));}
}
void Widget::itemFunc(QListWidgetItem *item)
{PluginBase *plugin = reinterpret_cast<PluginBase *>(item->data(Qt::UserRole).value<quintptr>());QWidget*widget = plugin->pluginSetupUI();if(!widget){qDebug()<<Q_FUNC_INFO<<"null.....";return ;}widget->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);widget->setParent(ui->widget);ui->widget->setLayout(widget->layout());widget->showMaximized();
}

其中较为重要的代码为

QVariant variant = QVariant::fromValue(reinterpret_cast<quintptr>(plugins[i]));item->setData(Qt::UserRole,variant );PluginBase *plugin = reinterpret_cast<PluginBase *>(item->data(Qt::UserRole).value<quintptr>());
上述代码来自写的插件演示demo程序,加载插件放置文件夹下的插件,并在list中显示出来,点击list 把插件设置区的插件实现的UI展示出来。

插件管理的实现

插件管理的实现主要是实现插件的安装与卸载,插件数据接收与发送

bool PluginManager::loadPlugin(const QString &pluginPath)
{if(!QLibrary::isLibrary(pluginPath)){qDebug()<<Q_FUNC_INFO<<pluginPath<<" not a plugin";return false;}//加载插件QPluginLoader *loader = new QPluginLoader(pluginPath);if(loader->load()){PluginBase *plugin = qobject_cast<PluginBase *>(loader->instance());if(plugin){plugin->registerPluginResponseFunc(&PluginManager::pluginResponse,this);plugin->init();m_loaders.append(loader);m_plugins.append(plugin);m_pluginPath.append(pluginPath);return true;}else{delete loader;loader = NULL;qDebug()<<Q_FUNC_INFO<<"Conversion PluginBase failure ";}}else{qDebug() << Q_FUNC_INFO <<"loadPlugin:"<<pluginPath<<loader->errorString();}return  false;
}void PluginManager::unloadPlugin(const QString &pluginPath)
{int index = m_pluginPath.indexOf(pluginPath);if(index<0)return ;QPluginLoader *loader = m_loaders[index];if(loader){m_plugins[index]->exit();//卸载插件,并从内部数据结构中移除if(loader->unload()){m_loaders.remove(index);m_pluginPath.remove(index);m_plugins.remove(index);delete loader;loader = NULL;}else{qDebug()<<Q_FUNC_INFO<<"unload fail"<<loader->isLoaded();}}
}
//插件数据发送,功能处理
bool PluginManager::pluginWorkFunction(const QString &functionName, const QJsonObject &data)
{bool ret = false;for(int i=0;i<m_plugins.size();i++){ret = m_plugins[i]->pluginWorkFunction(functionName,data);if(!ret){qDebug()<<Q_FUNC_INFO<<m_plugins[i]->pluginName()<<m_plugins[i]->version()<<m_plugins[i]->description()<<functionName<<"pluginWorkFunction execution failure";}}return ret;
}bool PluginManager::pluginResponse(const QString &functionName, const QJsonObject &data)
{//处理所有来自插件反馈给主程序的数据bool ret = true;if(m_responseFunc.contains(functionName)){ret = m_responseFunc[functionName](data);return ret;}else{return false;}
}

插件dll 的实现

1. qt 创建普通的动态库工程

2. 继承插件类,继承的插件基类增加

Q_DECLARE_INTERFACE(PluginBase,"xxxx.PluginInterface")
class PluginBase
{
public:virtual ~PluginBase(){};/*** @brief pluginName 插件名称会显示在插件管理系统中* @return 插件名称*/virtual QString pluginName()=0;/*** @brief version 插件版本会显示在插件管理系统中* @return 插件版本 如格式V1.0.0* @note 命名格式建议使用V1.X.X 格式*/virtual QString version()=0;/*** @brief description* @return 插件的描述 如xx系统对接插件*/virtual QString description()=0;/*** @brief pluginType plugin 类型* @return 返回插件类型名称* @note 在插件管理界面,不同的插件类型会被归于一组展示*/virtual QString pluginType()=0;...........virtual bool init()=0;virtual bool exit()=0;template<typename T1,typename T2>void registerPluginResponseFunc(T1 memberfunc,T2* pThis){m_responseCallback= std::bind(memberfunc,pThis,std::placeholders::_1,std::placeholders::_2);}protected:typedef std::function<bool (const QString&functionName,const QJsonObject&data)> Callback;Callback m_responseCallback;};
Q_DECLARE_INTERFACE(PluginBase,"xxxx.PluginInterface")
class MyPluin : public QObject ,public PluginBase
{Q_OBJECTQ_INTERFACES(PluginBase)Q_PLUGIN_METADATA(IID "xxxx.PluginInterface" FILE "pluginInfo.json" )
public:explicit MyPluin(QObject *parent=NULL);virtual ~MyPluin();//! 插件名QString pluginName();//! 插件版本 如格式V1.0.0QString version();//! 插件的描述 如xx系统对接插件QString description();//! plugin 类型QString pluginType();//! plugin功能函数bool pluginWorkFunction(const QString&functionName,const QJsonObject&data);//! 功能区的UIbool pluginWorkUI(const QString&functionName);//! 设置区的UIQWidget* pluginSetupUI();//! 插件初始化/加载bool init();//! 插件卸载/退出bool exit();private:QWidget* m_widget;};

注意生成的dll 在windows上没有任务厂家和版本信息,在pro qt工程中要增加version.rc

#if defined(UNDER_CE)
#include <winbase.h>
#else
#include <winver.h>
#endif#include "version.h"VS_VERSION_INFO VERSIONINFOFILEVERSION VER_FILEVERSIONPRODUCTVERSION VER_PRODUCTVERSION
BEGINBLOCK "StringFileInfo"BEGINBLOCK "080404b0"BEGINVALUE "CompanyName", "xxxx有限公司\0"VALUE "FileDescription", "xx演示demo用的插件\0"VALUE "FileVersion", VER_FILEVERSION_STRVALUE "ProductVersion", VER_PRODUCTVERSION_STRVALUE "LegalCopyright", "版权所有 (C) xxxx有限公司\0"VALUE "LegalTrademarks", "xxxx有限公司\0"VALUE "OriginalFilename", "PluginDemo.dll\0"VALUE "ProductName", "xxxx插件\0"VALUE "InternalName", "PluginDemo\0"ENDENDBLOCK "VarFileInfo"BEGINVALUE "Translation", 0x804, 1200END        
END

pro中增加

RC_FILE += version.rc

遇到的问题

1. unload卸载时插件无法解除占用,无法删除插件dll文件,除非应用程序退出; 目前想要卸载时删除dll,只能提示软件重启再删除,qt官方的解释:https://bugreports.qt.io/browse/QTBUG-68880

2. 在插件中是否可以像qt应用程序那样自定义UI,使用UI设计师进行ui设计,并且连接信号与槽了,答案是可以,但要注意,前提是你要自已创建一个widget类例如

class SetupWidget : public QWidget
{Q_OBJECT
public:explicit SetupWidget(QWidget *parent = nullptr);private slots:void on_buttonPlugin_clicked();private:Ui::Widget *ui;};

创建的UI文件不要在直接与前面的MyPluin关联,这样在操作UI时,在UI设计师中右键建立槽连接时,槽函数的创建才会关联到SetupWidget。

3. 插件的json不是必须,在插件安装中反而因为有多个文件,插件不方便。


http://www.ppmy.cn/news/1510975.html

相关文章

响应式Web设计:纯HTML和CSS的实现技巧

文章目录 响应式Web设计&#xff1a;纯HTML和CSS的实现技巧一、响应式Web设计概述二、实现响应式设计的技巧1. 使用媒体查询&#xff08;Media Queries&#xff09;2. 使用弹性布局&#xff08;Flexbox&#xff09;3. 使用网格布局&#xff08;CSS Grid&#xff09;4. 使用相对…

介绍Python `AsyncIterable` 的使用方法和使用场景

介绍Python AsyncIterable 的使用方法和使用场景 一、什么是 AsyncIterable&#xff1f;二、如何使用 AsyncIterable三、使用场景四、总结 在Python异步编程中&#xff0c;AsyncIterable 是一个非常重要的概念&#xff0c;它代表了一个异步可迭代对象。异步可迭代对象允许我们在…

【二叉树进阶】--- 二叉搜索树转双向链表 最近公共祖先

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构 本篇博客我们继续了解一些二叉树的进阶算法。 &#x1f3e0; 二叉搜索 树转化为双向循环链表 &#x1f4cc; 题目内容 将二叉搜索树转化为排序…

代码随想录算法训练营 | 贪心算法 part05

56. 合并区间 56. 合并区间 class Solution { public:static bool cmp(vector<int>& a, vector<int>& b) {if(a[0] b[0]) {return a[1] < b[1];}return a[0] < b[0];}vector<vector<int>> merge(vector<vector<int>>&…

Debian系统安装Docker

Debian系统安装Docker 更新软件包索引安装必要的软件包以允许apt通过HTTPS使用仓库添加Docker的官方GPG密钥设置Docker的稳定仓库再次更新软件包索引安装Docker CE&#xff08;社区版&#xff09;验证Docker是否安装成功 更新软件包索引 sudo apt-get update安装必要的软件包以…

linux内核启动流程

内核启动流程 准备工作&#xff1a;关闭 MMU、关闭 D-cache&#xff08;数据缓存&#xff09;&#xff08;I-Cache 指令缓存无所谓&#xff09; 第一阶段&#xff1a;内核引导阶段&#xff1a;汇编语言设置ARM处理器工作模式、使能MMU、设置一级页表&#xff0c;调用start_ke…

部署 K8s 图形化管理工具 Dashboard

文章目录 一、Dashboard 概述二、GitHub 地址三、Dashboard 部署安装1、选择兼容版本2、下载配置文件3、添加 Dashboard 的Service类型4、应用部署5、查看 kubernetes-dashboard 命名空间下资源状态6、创建访问账户7、授权8、获取账号token9、1.24 版本以后的需要创建一个Pod 四…

多种方案解决IOS下uni.share分享分包页面报错Error: Framework inner error

项目场景&#xff1a; 有个需求是用uni.share从app分享微信小程序&#xff0c;发现在苹果手机真机调试的时候 跳转的目标页面会白屏、页面样式错乱、一些组件不出现等问题。并且报错 Error: Framework inner error 问题描述 uniapp开发在苹果手机下app分享微信小程序会出现白…