Плагины(Расширения)
Расширения это совместно используемая динамическая библиотека предназначенная для загрузки в процессе исполнения основного приложения, которая обязательно должна реализовывать хотя бы один специальный интерфейс.
Расширения делятся на два типа:
- Для 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()); } //-------------------------------------------------------------------
Вывод основной программы:

