Creating an extension system on the Qt library

Plugins (Extensions)


Extensions is a shared dynamic library designed to be loaded during the execution of the main application, which must necessarily implement at least one special interface.

Extensions are divided into two types:

  • For Qt
  • For native applications

Let's figure out how to create your own extension system and the extensions themselves for it.

Communication with the extension is carried out using the interface (signals, slots and class methods). The extension is loaded by the application using the QPluginLoader class . To load the extension, the instance () method is used , which creates an extension object and returns a pointer to it. The unload () method is used to unload the extension .

Part 1


In the first example, create an extension that will use a function (algorithm, formula) from the extension.

The visual scheme of the project will look as follows.



Stage 1:


The first step is to create an interface class inherited from QObject, as an interface there will be a method that accepts a variable of type QString and returns the same string in upper case. Using the Q_DECLARE_INTERFACE macro, we set the identifier of the interfaces, the compiler c generates meta-information for the identifier string. This module is the communication protocol between the plug-in and the main program and will be used in the plug-in project and in the main project.

The class will look as follows.

//---------------------------------------------------
#ifndef INTERFACE_H
#define INTERFACE_H
//-------------------------------------------------------
#include 
//-------------------------------------------------------
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
//----------------------------------------------------------------


Stage 2:


Let's create a basic application that will download the extension. By pressing the button, the extension will be searched and loaded into the system. Further through the interface we will use our function.

Base application:

mainproject.h

//---------------------------------------------------
#ifndef MAINPROJECT_H
#define MAINPROJECT_H
//-------------------------------------------------------
#include 
#include 
#include 
#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(loader.instance());
          if(!pobj)
              continue;
          pluginObject = 0;
          // Получения интерфейсов
          pluginObject = qobject_cast(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);
}
//-------------------------------------------------------


Stage 3:


Creating an extension, the first thing to do is change the type of the project being built in the pro file, for this you need to add the following line TEMPLATE = lib, and set the project configuration for the CONFIG + = plugin extension.

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

Next, we create a class for the future extension, the class should be inherited from the class of interfaces. Macro Q_INTERFACES , you need the compiler to generate all the necessary meta information for the extension. The Q_PLUGIN_METADATA () macro sets the entry point to the extension and access for the Qt library. You also need to create an inteface.json file with meta-information (the file must be in the root of the project), in our case there is no information there, so just write empty quotation marks {} in the file.

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();
}
//---------------------------------------------------

At the output of compiling the project, we get a file with the extension .so, move this file to the Plugins folder of the main project, and start it. In this case, the extension is loaded into the main program and a single extension object is created. If you try to reuse the instance () function, the function will return a pointer to the already created extension object.

Program execution



Part 2


To complicate our task, now we need the extension to be a widget and the ability to create several such widgets. The main program will receive messages from plugins and send back a response. We will create new projects, at the first stage we will need two classes of interfaces, one will be responsible for loading the extension and creating the widget, the other for the operation of the widget itself.

The project scheme will look as follows:



Stage 1:


The first interface class will have two functions, get the name of the plugin and get the plugin widget. The name of the plugin will be stored for identification in the system. We will add the plugin widget to the MDI windows of the main application.

The second class is the graphical widget itself, it is inherited from QWidget, here we have specified the functions we need, the widget will receive a message and send it to the main program.

interface.h

//-------------------------------------------------------------------------
#ifndef INTERFACE_H
#define INTERFACE_H
//-------------------------------------------------------------------------
#include 
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
//-------------------------------------------------------------------------

Stage 2:


The main program consists of an MDI window, in which there is a main widget for receiving messages from plugins and additional windows that dynamically appear as the plugins are called.

When creating a plugin widget, we connect the signal from the plugin to the slot and using the sender () function we get a pointer to the plugin that sent the message. We place the created widget in the MDI window, and the plug-in object itself can be unloaded from the system.

mainproject.h

//------------------------------------------------
#ifndef MAINPROJECT_H
#define MAINPROJECT_H
//------------------------------------------------
#include 
#include 
#include 
#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 vecPlugin;        ///< Вектор плагинов
};
//------------------------------------------------
#endif // MAINPROJECT_H
//------------------------------------------------

mainproject.cpp

//------------------------------------------------
#include "mainproject.h"
#include "ui_mainproject.h"
#include "mainwidget.h"
#include 
//------------------------------------------------
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(loader.instance());
            if(!pobj)
              continue;
            interface *plW = 0;
            plW = qobject_cast(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(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(loader.instance());
                   if(!pobj)
                      continue;
                   interface *plW = qobject_cast(pobj);
                   if(!plW)
                      continue;
                   QObject *ob = plW->getPluginWidget();
                    if(!ob)
                      continue;
                    interfaceWidget *interFaceW = dynamic_cast(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(pobj);
        widget->slot_getText("Получено сообщение от плагина");
        widget→slot_getText(str);
        widget->slot_getText("Отправлен ответ");
        widget→slot_getText("------------------------------");
        pPlug->slot_getText("Сообщение доставлено");
}
//------------------------------------------------

The main window accepts the message and displays it.

mainwidget.h

//----------------------------------------------------------
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
//----------------------------------------------------------
#include 
//----------------------------------------------------------
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);
}
//----------------------------------------------------------

Stage 2:


We create a plugin, its idea is that it is a factory for creating a widget.

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(widget);
}
//-------------------------------------------------

Widget created by the plugin.

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());
}
//-------------------------------------------------------------------

The output of the main program:


Also popular now: