前言
上文写了插件管理系统的设计思路,这一篇文章主要介绍插件的实现。主要从三个方面介绍实现;
- 插件加载的实现
- 插件管理的实现;
- 插件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_OBJECT
Q_INTERFACES(PluginBase)
Q_PLUGIN_METADATA(IID "xxxx.PluginInterface" FILE "pluginInfo.json" )
public:
explicit MyPluin(QObject *parent=NULL);
virtual ~MyPluin();
//! 插件名
QString pluginName();
//! 插件版本 如格式V1.0.0
QString version();
//! 插件的描述 如xx系统对接插件
QString description();
//! plugin 类型
QString pluginType();
//! plugin功能函数
bool pluginWorkFunction(const QString&functionName,const QJsonObject&data);
//! 功能区的UI
bool pluginWorkUI(const QString&functionName);
//! 设置区的UI
QWidget* 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 VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080404b0"
BEGIN
VALUE "CompanyName", "xxxx有限公司\0"
VALUE "FileDescription", "xx演示demo用的插件\0"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
VALUE "LegalCopyright", "版权所有 (C) xxxx有限公司\0"
VALUE "LegalTrademarks", "xxxx有限公司\0"
VALUE "OriginalFilename", "PluginDemo.dll\0"
VALUE "ProductName", "xxxx插件\0"
VALUE "InternalName", "PluginDemo\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x804, 1200
END
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不是必须,在插件安装中反而因为有多个文件,插件不方便。