Плагины(Расширения)
Расширения это совместно используемая динамическая библиотека предназначения для загрузки в процессе исполнения основного приложения, которая обязательно должна реализовывать хотя бы один специальный интерфейс.
Расширения делятся на два типа:
- Для Qt
- Для собственных приложений
Разберём как создать свою систему расширений и сами расширения для него.
Связь с расширением осуществляется с помощью интерфейса (сигналы, слоты и методы класса). Расширение загружается приложением при помощи класса QPluginLoader. Для загрузки расширения используется метод instance(), который создаёт объект расширения и возвращает указатель на него. Для выгрузки расширения используется метод unload().
Часть 1
В первом примере создадим расширение которое будет использовать функцию(алгоритм, формулу) из расширения.
Визуальная схема проекта будет выглядеть следующим образом.
Этап 1:
Первым этапом создадим класс интерфейсов наследуемый от Qobject, в качестве интерфейса будет метод который принимает переменную типа QString и возвращает эту же строку в верхнем регистре. С помощью макроса Q_DECLARE_INTERFACE, задаём идентификотор интерфейсов, компилятор с генерирует метаинформацию для строки-идентификатор. Данный модуль является протоколом общения между плагином и основной программой и будет использоваться в проекте плагина и в основном проекте.
Класс будет выглядеть следующем образом.
//---------------------------------------------------
#ifndef INTERFACE_H
#define INTERFACE_H
//-------------------------------------------------------
#include <QObject>
//-------------------------------------------------------
class interface : public QObject
{
public:
/// brief виртуальный деструктор
virtual ~interface() = default;
/// brief Интерфейс расширения
virtual QString getUpString(QString str) = 0;
};
//----------------------------------------------------------------
Q_DECLARE_INTERFACE(interface, "com.mysoft.Application.interface")
//----------------------------------------------------------------
#endif // INTERFACE_H
//----------------------------------------------------------------
Этап 2:
Создадим базовое приложение которое будет загружать расширение. По нажатию кнопку будет происходить поиск расширения и загрузка его в систему. Далее через интерфейс будем задействовать нашу функцию.
Базовое приложение:
mainproject.h
//---------------------------------------------------
#ifndef MAINPROJECT_H
#define MAINPROJECT_H
//-------------------------------------------------------
#include <QWidget>
#include <QPluginLoader>
#include <QDir>
#include "interface.h"
//-------------------------------------------------------
namespace Ui {
class mainProject;
}
//-------------------------------------------------------
class mainProject : public QWidget
{
Q_OBJECT
public:
/// brief конструктор
explicit mainProject(QWidget *parent = nullptr);
/// brief деструктор
~mainProject();
private slots:
/// brief Поиск плагина
void on_searchPlugin_clicked();
/// brief Использования интерфейса
void on_getUp_clicked();
private:
Ui::mainProject *ui;
interface *pluginObject; ///< Указатель на объект плагина
};
//-------------------------------------------------------
#endif // MAINPROJECT_H
//-------------------------------------------------------
mainproject.cpp
//---------------------------------------------------
#include "mainproject.h"
#include "ui_mainproject.h"
//-------------------------------------------------------
mainProject::mainProject(QWidget *parent) :
QWidget(parent),
ui(new Ui::mainProject)
{
ui->setupUi(this);
}
//-------------------------------------------------------
mainProject::~mainProject()
{
delete ui;
}
//-------------------------------------------------------
void mainProject::on_searchPlugin_clicked()
{
QStringList listFiles;
QDir dir(QApplication::applicationDirPath() + "/Plugins/");
// Поиск всех файлов в папке "Plugins"
if(dir.exists())
listFiles = dir.entryList(QStringList("*"), QDir::Files);
// Проход по всем файлам
for(QString str: listFiles)
{
QPluginLoader loader(dir.absolutePath() + "/" +str);
QObject *pobj = 0;
// Загрузка плагина
pobj = qobject_cast<QObject*>(loader.instance());
if(!pobj)
continue;
pluginObject = 0;
// Получения интерфейсов
pluginObject = qobject_cast<interface *>(pobj);
// Проверка тот ли загружен плагин
if(pluginObject)
{
ui->label->setText("Расширение найдено");
break;
}
}
}
//-------------------------------------------------------
void mainProject::on_getUp_clicked()
{
QString tmp;
tmp = ui->lineEdit->text();
// использование интерфейса getUpString()
tmp = pluginObject->getUpString(tmp);
ui->label_2->setText(tmp);
}
//-------------------------------------------------------
Этап 3:
Создание расширения, первое что нужно сделать это в pro файле изменить типа собираемого проекта, для этого нужно добавить следующую строку TEMPLATE = lib, и задать конфигурацию проекта под расширения CONFIG += plugin.
upperstringplugin.pro
#-------------------------------------------------
#
# Project created by QtCreator 2019-04-03T11:35:18
#
#-------------------------------------------------
QT += core
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = upperStringPlugin
TEMPLATE = lib
CONFIG += plugin
DESTDIR = ../Plugins
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++11
SOURCES +=
upperstringplugin.cpp
HEADERS +=
upperstringplugin.h
interface.h
Далее создаём класс будущего расширения, класс должен быть унаследован от класса интерфейсов. Макрос Q_INTERFACES, нужен что бы компилятор с генерировал всю необходимую мета информацию для расширения. Макрос Q_PLUGIN_METADATA(), задаёт точку входа в расширение и доступ для библиотеки Qt. Также нужно создать файл inteface.json с метаинформацией(файл должен находиться в корне проекта), в нашем случае там нет информации поэтому просто запишем пустые кавычки {} в файл.
upperstringplugin.h
//---------------------------------------------------
#ifndef UPPERSTRINGPLUGIN_H
#define UPPERSTRINGPLUGIN_H
//---------------------------------------------------
#include "interface.h"
//---------------------------------------------------
class upperStringPlugin : public interface
{
Q_OBJECT
Q_INTERFACES(interface)
Q_PLUGIN_METADATA(IID "com.mysoft.Application.interface" FILE "interface.json")
public:
explicit upperStringPlugin();
~upperStringPlugin();
// interface interface
public:
QString getUpString(QString str);
};
//---------------------------------------------------
#endif // UPPERSTRINGPLUGIN_H
//---------------------------------------------------
upperstringplugin.cpp
//---------------------------------------------------
#include "upperstringplugin.h"
//---------------------------------------------------
upperStringPlugin::upperStringPlugin()
{}
//---------------------------------------------------
upperStringPlugin::~upperStringPlugin()
{}
//---------------------------------------------------
QString upperStringPlugin::getUpString(QString str)
{
return str.toUpper();
}
//---------------------------------------------------
На выходе компиляции проекта мы получим файл с расширением .so, данный файл перемещаем в папку «Plugins» главного проекта и запускаем его. В данном случае расширение загружается в основную программу и создаётся единственный объект расширения. При попытки заново использовать функцию instance(), функция вернёт указатель на уже созданный объект расширения.
Выполнение программы
Часть 2
Усложним нашу задачу, теперь нам нужно что бы расширение представляло собой виджет и что бы была возможность создавать несколько таких виджетов. Основная программа будет принимать сообщения от плагинов и отправлять обратно ответ. Создадим новые проекты, на первом этапе нам понадобиться два класса интерфейсов, один будет отвечать за загрузку расширения и создания виджета, другой за работу самого виджета.
Схема проекта будет выглядеть следующим образом:
Этап 1:
Первый класс interface будет иметь две функции, получить названия плагина и получить виджет плагина. Название плагина будем хранить для индентификации его в системе. Виджет плагина мы будем добавлять в MDI окна основного приложения.
Второй класс является самим графическим виджетом, он унаследован от Qwidget, здесь мы прописали необходимые нам функции, виджет будет получать сообщение и отправлять в основную программу.
interface.h
//-------------------------------------------------------------------------
#ifndef INTERFACE_H
#define INTERFACE_H
//-------------------------------------------------------------------------
#include <QWidget>
class QString;
//-------------------------------------------------------------------------
class interface : public QObject
{
public:
/// brief Деструктор
virtual ~interface(){}
/// brief Получить название плагина
virtual QString getNamePlugin() = 0;
/// brief Получить новый виджет
virtual QObject *getPluginWidget() = 0;
};
//-------------------------------------------------------------------------
class interfaceWidget: public QWidget
{
public:
/// brief Деструктор
virtual ~interfaceWidget() = default;
signals:
/// brief Сигнал отправляет текст из расширения
virtual void signal_writeText(QString str) = 0;
public slots:
/// brief Слот получает текст в плагин
virtual void slot_getText(QString str) = 0;
};
//-------------------------------------------------------------------------
Q_DECLARE_INTERFACE(interface, "com.mysoft.Application.interface")
//-------------------------------------------------------------------------
#endif // INTERFACE_H
//-------------------------------------------------------------------------
Этап 2:
Основная программа состоит из MDI окна, в котором есть основной виджет для принятия сообщений от плагинов и дополнительные окна, которые динамически появляются по мере вызова плагинов.
При создания виджета плагина мы соединяем сигнал от плагина со слотом и с помощью функции sender() мы получаем указатель на плагин который прислал сообщение. Созданный виджет мы помещаем в MDI окно, а сам объект плагина можно выгрузить из системы.
mainproject.h
//------------------------------------------------
#ifndef MAINPROJECT_H
#define MAINPROJECT_H
//------------------------------------------------
#include <QMainWindow>
#include <QDir>
#include <QPluginLoader>
#include "interface.h"
//------------------------------------------------
namespace Ui {
class mainProject;
}
//------------------------------------------------
typedef struct str_plugin
{
QString namePlugin; ///< Имя плагина
QString dirPlugin; ///< Расположение плагина
}TSTR_PLUGIN;
//------------------------------------------------
class mainWidget;
//------------------------------------------------
class mainProject : public QMainWindow
{
Q_OBJECT
public:
explicit mainProject(QWidget *parent = nullptr);
~mainProject();
private slots:
void on_action_triggered();
/// brief Слот Запуска плагина
void slot_showPlugin();
/// brief Функция получения текста от плагина и отправляет ему ответ
void slot_getTextFromPlugin(QString str);
private:
Ui::mainProject *ui;
mainWidget *widget; ///< Основное окно
QVector<TSTR_PLUGIN > vecPlugin; ///< Вектор плагинов
};
//------------------------------------------------
#endif // MAINPROJECT_H
//------------------------------------------------
mainproject.cpp
//------------------------------------------------
#include "mainproject.h"
#include "ui_mainproject.h"
#include "mainwidget.h"
#include <QMdiSubWindow>
//------------------------------------------------
mainProject::mainProject(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::mainProject)
{
ui->setupUi(this);
QMdiSubWindow *sWPS = new QMdiSubWindow;
widget = new mainWidget();
sWPS->setWidget(widget);
ui->mdiArea->addSubWindow(sWPS);
}
//------------------------------------------------
mainProject::~mainProject()
{
delete ui;
}
//------------------------------------------------
void mainProject::on_action_triggered()
{
ui->menu_2->clear();
QStringList listFiles;
QDir dir(QApplication::applicationDirPath() + "/Plugins/");
if(dir.exists())
{
listFiles = dir.entryList(QStringList("*"), QDir::Files);
}
for(QString str: listFiles)
{
QPluginLoader loader(dir.absolutePath() + "/" +str);
QObject *pobj = 0;
pobj = qobject_cast<QObject*>(loader.instance());
if(!pobj)
continue;
interface *plW = 0;
plW = qobject_cast<interface *>(pobj);
if(!plW)
continue;
QString namePlugin = plW->getNamePlugin();
QAction *action = new QAction(namePlugin);
ui->menu_2->addAction(action);
connect(action, SIGNAL(triggered()), this, SLOT(slot_showPlugin()));
TSTR_PLUGIN plug;
plug.namePlugin = namePlugin;
plug.dirPlugin = dir.absolutePath() + "/" +str;
vecPlugin.push_back(plug);
delete plW;
}
}
//------------------------------------------------
void mainProject::slot_showPlugin()
{
QObject *pobj = sender();
QAction *action = qobject_cast<QAction *>(pobj);
QString namePlugin = action->iconText();
for(int i = 0; i < vecPlugin.size(); i++)
{
if(namePlugin == vecPlugin[i].namePlugin)
{
QMdiSubWindow *sWPS = new QMdiSubWindow;
ui->mdiArea->addSubWindow(sWPS);
sWPS->setAttribute(Qt::WA_DeleteOnClose, true);
QPluginLoader loader(vecPlugin[i].dirPlugin);
QObject *pobj = qobject_cast<QObject*>(loader.instance());
if(!pobj)
continue;
interface *plW = qobject_cast<interface *>(pobj);
if(!plW)
continue;
QObject *ob = plW->getPluginWidget();
if(!ob)
continue;
interfaceWidget *interFaceW = dynamic_cast<interfaceWidget *>(ob);
if(!interFaceW)
continue;
sWPS->setWidget(interFaceW);
sWPS->show();
QSize size = interFaceW->minimumSize();
size.setHeight(size.height() + 20);
size.setWidth(size.width() + 20);
sWPS->resize(size);
loader.unload();
connect(interFaceW, SIGNAL(signal_writeText(QString)), this, SLOT(slot_getTextFromPlugin(QString)));
}
}
}
//------------------------------------------------
void mainProject::slot_getTextFromPlugin(QString str)
{
//Получение указателя на отправителя сообщения
QObject *pobj = sender();
interfaceWidget *pPlug = dynamic_cast<interfaceWidget *>(pobj);
widget->slot_getText("Получено сообщение от плагина");
widget→slot_getText(str);
widget->slot_getText("Отправлен ответ");
widget→slot_getText("------------------------------");
pPlug->slot_getText("Сообщение доставлено");
}
//------------------------------------------------
Основное окно, принимает сообщение и отображает его.
mainwidget.h
//----------------------------------------------------------
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
//----------------------------------------------------------
#include <QWidget>
//----------------------------------------------------------
namespace Ui {
class mainWidget;
}
//----------------------------------------------------------
class mainWidget : public QWidget
{
Q_OBJECT
public:
explicit mainWidget(QWidget *parent = nullptr);
~mainWidget();
public slots:
/// brief Слот принимает сообщения от плагинов
void slot_getText(QString str);
private:
Ui::mainWidget *ui;
};
//----------------------------------------------------------
#endif // MAINWIDGET_H
//----------------------------------------------------------
mainwidget.cpp
//----------------------------------------------------------
#include "mainwidget.h"
#include "ui_mainwidget.h"
//----------------------------------------------------------
mainWidget::mainWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::mainWidget)
{
ui->setupUi(this);
}
//----------------------------------------------------------
mainWidget::~mainWidget()
{
delete ui;
}
//----------------------------------------------------------
void mainWidget::slot_getText(QString str)
{
ui->textEdit->append(str);
}
//----------------------------------------------------------
Этап 2:
Создаём плагин, его идея состоит в том что он является фабрикой для создания виджета.
plugin.h
//-------------------------------------------------
#ifndef PLUGIN_H
#define PLUGIN_H
//-------------------------------------------------
#include "interface.h"
#include "texttranferwidget.h"
//-------------------------------------------------
class plugin : public interface
{
Q_OBJECT
Q_INTERFACES(interface)
Q_PLUGIN_METADATA(IID "com.mysoft.Application.interface" FILE "interface.json")
public:
explicit plugin();
~plugin();
// interface interface
public:
/// brief Получить название плагина
QString getNamePlugin();
/// brief Получить новый виджет
QObject *getPluginWidget();
};
//-------------------------------------------------
#endif // PLUGIN_H
//-------------------------------------------------
plugin.cpp
//-------------------------------------------------
#include "plugin.h"
//-------------------------------------------------
plugin::plugin()
{
}
//-------------------------------------------------
plugin::~plugin()
{
}
//-------------------------------------------------
QString plugin::getNamePlugin()
{
return "Тестовый плагин1";
}
//-------------------------------------------------
QObject *plugin::getPluginWidget()
{
textTranferWidget *widget = new textTranferWidget();
return qobject_cast<QObject *>(widget);
}
//-------------------------------------------------
Виджет создаваемый плагином.
texttranferwidget.h
//-------------------------------------------------------------------
#ifndef TEXTTRANFERWIDGET_H
#define TEXTTRANFERWIDGET_H
//-------------------------------------------------------------------
#include "interface.h"
//-------------------------------------------------------------------
namespace Ui {
class textTranferWidget;
}
//-------------------------------------------------------------------
class textTranferWidget : public interfaceWidget
{
Q_OBJECT
public:
/// brief Конструктор
explicit textTranferWidget();
/// brief Деструктор
~textTranferWidget();
private:
Ui::textTranferWidget *ui;
// interfaceWidget interface
signals:
/// brief Сигнал отправляет текст из расширения
void signal_writeText(QString str);
public slots:
/// brief Слот получает текст в плагин
void slot_getText(QString str);
private slots:
void on_pushButton_clicked();
};
//-------------------------------------------------------------------
#endif // TEXTTRANFERWIDGET_H
//-------------------------------------------------------------------
texttranferwidget.cpp
//-------------------------------------------------------------------
#include "texttranferwidget.h"
#include "ui_texttranferwidget.h"
//-------------------------------------------------------------------
textTranferWidget::textTranferWidget() :
ui(new Ui::textTranferWidget)
{
ui->setupUi(this);
}
//-------------------------------------------------------------------
textTranferWidget::~textTranferWidget()
{
delete ui;
}
//-------------------------------------------------------------------
void textTranferWidget::slot_getText(QString str)
{
ui->textEdit->append(str);
}
//-------------------------------------------------------------------
void textTranferWidget::on_pushButton_clicked()
{
emit signal_writeText(ui->lineEdit->text());
}
//-------------------------------------------------------------------
Вывод основной программы:
Автор: CIUK