使用插件开发都是为了扩展应用程序的功能。
Qt如何开发插件,可见帮助文档。
Qt本身和Qt应用程序都可以通过插件进行扩展。这需要应用程序使用QPluginLoader检测和加载插件。在这种情况下,插件可以提供任意的功能,而不限于数据库驱动程序、图像格式、文本编解码器、样式和其他扩展Qt功能的插件。
Qt扩展应用程序
要想使用插件来扩展应用程序,那么首先在主程序中的步骤如下:
定义一组用于与插件通信的接口(只有纯虚函数的类)
使用 Q_DECLARE_INTERFACE() 宏来告诉 Qt 元对象系统有关接口的情况
在应用程序中使用 QPluginLoader 加载插件
使用 qobject_cast() 来测试插件是否实现了指定的接口
编写扩展 Qt 应用程序的插件,步骤如下:
声明一个继承自 QObject 和插件想要提供的接口的插件类
使用 Q_INTERFACES() 宏来告诉 Qt 元对象系统有关接口的情况
使用 Q_PLUGIN_METADATA() 宏导出插件
使用合适的 .pro 文件构建插件
示例:
环境:Qt5.9.7+MSVC20015 32
步骤:
1.新建一个子目录项目
创建好后会提示创建一个子项目,这里直接创建一个mainwindow主工程项目,并命名为frmMain。
接着,我们再创建一个插件的工程,右击主工程,添加子项目:
选择一个空项目添加:Empty qmake Project.
主工程和插件工程已创建完成。
2.定义通信接口
接下来在主工程中新建一个头文件,用于定义与插件通信的接口(只有纯虚函数的类)
#ifndef ARITH_H
#define ARITH_H
//定义接口
class arithInterface
{
public:
virtual ~arithInterface(){}
virtual int add(int a,int b)=0;
virtual int div(int a,int b)=0;
virtual int multiply(int a,int b)=0;
virtual int sub(int a,int b)=0;
};
#define arithInterface_iid "Examples.Pulgin.arithInterface"
//arithInterface_iid 宏定义字符串一定要是唯一的,然后使用宏Q_DECLARE_INTERFACE来声明该接口。
QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(arithInterface,arithInterface_iid)
QT_END_NAMESPACE
#endif // ARITH_H
为方面演示,这里只做了非常简单的加法运算,所以创建了一个纯虚函数接口add,这里的CalInterface_iid
宏定义字符串一定要是唯一的,然后使用宏Q_DECLARE_INTERFACE
来声明该接口。
3.编写插件
插件通信接口写完了,然后开始来编写插件,在插件工程下创建一个新的类,同时继承于QObject和通信接口类CalInterface,如下:
#ifndef ARITHPLUGIN_H
#define ARITHPLUGIN_H
#include <QObject>
#include<QtPlugin>
#include"../frmMain/arith.h"
class arithPlugin : public QObject,public arithInterface
{
Q_OBJECT
Q_INTERFACES(arithInterface)
Q_PLUGIN_METADATA(IID arithInterface_iid FILE "arithplugin.json")
public:
explicit arithPlugin(QObject *parent = nullptr);
int add(int a,int b);
int div(int a,int b);
int multiply(int a,int b);
int sub(int a,int b);
};
#endif // ARITHPLUGIN_H
#include "arithplugin.h"
arithPlugin::arithPlugin(QObject *parent) : QObject(parent)
{
}
int arithPlugin::add(int a, int b)
{
return a+b;
}
int arithPlugin::div(int a, int b)
{
if(b!=0)
return a/b;
else
return -1;
}
int arithPlugin::multiply(int a, int b)
{
return a*b;
}
int arithPlugin::sub(int a, int b)
{
return a-b;
}
注意,在头文件中一定要用Q_INTERFACES宏来声明接口类。
这里的calplugin.json是为插件提供插件信息的,并使用Q_PLUGIN_METADATA声明(实例化该对象的)插件的元数据,元数据是插件的一部分。
Q_PLUGIN_METADATA这个宏所在的类必须是默认可构造的。
FILE 是可选的,并指向一个 Json 文件。
Json 文件必须位于构建系统指定的包含目录之一中。当无法找到指定的文件时,moc 会出现错误。
如果不想为插件提供信息,当然不会有任何问题,只需保证 Json 文件为空就行。
4.修改插件类pro文件
由于插件编译出来也是dll的形式,所以我们需要在工程文件中将类型改成lib,以及其他的一些配置,如下:
TEMPLATE = lib
CONFIG += plugin
QT += widgets
INCLUDEPATH += ../frmMain
TARGET = $$qtLibraryTarget(arithplugin)
DESTDIR = ../plugins
EXAMPLE_FILES = arithplugin.json
HEADERS += \
arithplugin.h
SOURCES += \
arithplugin.cpp
CONFIG += install_ok
修改frmMain.pro文件
#-------------------------------------------------
#
# Project created by QtCreator 2021-03-03T11:45:38
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = frmMain
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
CONFIG += c++11
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h \
arith.h
FORMS += \
mainwindow.ui
win32 {
CONFIG(debug, release|debug):DESTDIR = ../debug/
CONFIG(release, release|debug):DESTDIR = ../release/
} else {
DESTDIR = ../
}
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
CONFIG += install_ok
5.主工程中调用插件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include"arith.h"
#include<QDebug>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_calBtn_clicked();
private:
bool loadPlugin();
private:
Ui::MainWindow *ui;
arithInterface *m_pInterface = nullptr;
};
#endif // MAINWINDOW_H
#pragma execution_character_set("utf-8")
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtWidgets>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
if(!loadPlugin())
QMessageBox::information(this,"info","plugin is not exist!");
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::loadPlugin()
{
QDir pluginsDir(qApp->applicationDirPath());
qDebug()<<qApp->applicationDirPath();
#if defined(Q_OS_WIN)
if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
pluginsDir.cdUp();
#elif defined(Q_OS_MAC)
if (pluginsDir.dirName() == "MacOS") {
pluginsDir.cdUp();
pluginsDir.cdUp();
pluginsDir.cdUp();
}
#endif
pluginsDir.cd("plugins");
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
qDebug() << "--->>>Lynn<<<---" << __FUNCTION__ << pluginLoader.errorString();
if (plugin) {
m_pInterface = qobject_cast<arithInterface *>(plugin);
if (m_pInterface)
return true;
}
}
return false;
}
void MainWindow::on_calBtn_clicked()
{
int a = ui->lineEditA->text().toInt();
int b = ui->lineEditB->text().toInt();
int r = -1;
if(m_pInterface)
{
if(ui->comboBox->currentText() == "+")
{
r = m_pInterface->add(a,b);
}
else if(ui->comboBox->currentText() == "-")
{
r = m_pInterface->sub(a,b);
}
else if(ui->comboBox->currentText() == "*")
{
r = m_pInterface->multiply(a,b);
}
else if(ui->comboBox->currentText() == "/")
{
r = m_pInterface->div(a,b);
}
else{}
ui->lineEditC->setText(QString::number(r));
}
}
其中loadPlugin()函数就是用于加载本地的插件文件,以上是动态加载的方式实现。