Qt plug-in development summary -- plug-in manager

Posted by wshell on Thu, 17 Feb 2022 10:58:38 +0100

1, Foreword

Plug in development summary - creation and use of plug-ins This article shows how to use QT low API plug-in example in QT. However, this can not meet the actual scenario of large-scale applications and has no scalability. The communication between plug-ins, loading and unloading (releasing memory), plug-in metadata, plug-in life cycle, plug-in dependency and other issues are what we need to do. In QT, PluginManager is responsible for high-level APIs, but low-level APIs need to write their own plug-in manager to help us solve these problems.

Imagine a computer with Windows system, including host, display screen, keyboard and mouse and other components. If we unplug the keyboard, the computer will not make mistakes, but the function of the keyboard is missing, so the keyboard can be regarded as a plug-in. At the same time, a complete computer includes not only keyboard and mouse, but also headphones, stereo, optical drive, graphics card and other components. In fact, these components can be regarded as plug-ins. For windows, these "plug-ins" have a manager, which is the device manager. The device manager is responsible for adding and deleting all the hardware and drivers of the computer. Therefore, the device manager can be understood as a plug-in manager. Finally, computer systems have their own kernel. The kernel responds from Startup to shutdown of a Windows system, and the kernel can be regarded as the main program for loading plug-ins, as if: "once you plug in, I can use you to play games".

2, Project structure


In the above program, the main program and plug-in program are separated, which is inconvenient to manage. In this paper, the parent-child engineering structure is adopted, as shown in the figure above, and the creation process is as follows:
1. Create a subdirectory item

2. Create subproject

Main, pluginA and pluginB are all sub projects

PluginInterface.h

#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H

#include <QObject>
#include <QJsonObject>

struct PluginMetaData
{
    QString from;   //Source
    QString dest;   //Message destination
    QString msg;    //news

    QObject* object = nullptr;
    QJsonObject info = QJsonObject();
};
Q_DECLARE_METATYPE(PluginMetaData); //Ensure that the type can be transmitted through the signal slot

//Define interface
class PluginInterface
{
public:
    virtual ~PluginInterface(){}
    virtual void recMsgFromManager(PluginMetaData) = 0; //Receive messages from the plug-in manager
    virtual void sendMsgToManager(PluginMetaData) = 0;  //A message occurs to the plug-in manager
};

//Must be a unique identifier
#define PluginInterface_iid "Examples.Plugin.PluginInterface"

QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(PluginInterface,PluginInterface_iid)
QT_END_NAMESPACE

#endif // PLUGININTERFACE_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QMessageBox>
#include <QDebug>

#include "PluginInterface.h"
#include "PluginManager.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H


widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    PluginManager::instance()->loadAllPlugins();
    //qDebug()<<"allPluginsName: "<<PluginManager::instance()->allPluginsName();

    QPluginLoader *loader1 = PluginManager::instance()->getPlugin("pluginA");
    if(loader1) {
        PluginInterface *pluginA = dynamic_cast<PluginInterface*>(loader1->instance());
        if(pluginA) {
            PluginMetaData m;
            m.dest = "pluginB";
            m.from = "pluginA";
            m.msg = "plug-in unit A Send to plugin B News of";
            pluginA->sendMsgToManager(m);
        }
    }

    QPluginLoader *loader2 = PluginManager::instance()->getPlugin("pluginB");
    if(loader2) {
        PluginInterface *pluginB = dynamic_cast<PluginInterface*>(loader2->instance());
        if(pluginB) {
            PluginMetaData m;
            m.dest = "pluginA";
            m.from = "pluginB";
            m.msg = "plug-in unit B Send to plugin A News of";
            pluginB->sendMsgToManager(m);
        }
    }

}

Widget::~Widget()
{
    delete ui;
}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

pluginA.pro

QT += widgets

TEMPLATE = lib          #Indicates that this makefile is a lib makefile
CONFIG += plugin        #The application is a plug-in

TARGET = pluginA        #Plug in name
DESTDIR = ../plugins    # Output directory

HEADERS += \
    pluginA.h

SOURCES += \
    pluginA.cpp

DISTFILES += \
    pluginA.json

pluginA.h

#ifndef PLUGINA_H
#define PLUGINA_H

#include <QObject>
#include <QtPlugin>
#include <QDebug>
#include "../Main/PluginInterface.h"

class PluginA : public QObject,public PluginInterface
{
    Q_OBJECT
    Q_INTERFACES(PluginInterface)
    Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "pluginA.json")
public:
    explicit  PluginA(QObject *parent = nullptr);
    void show_pluginA();

    void recMsgFromManager(PluginMetaData metaData);

signals:
    void sendMsgToManager(PluginMetaData);
};

#endif // PLUGINA_H

pluginA.cpp

#include "pluginA.h"

PluginA::PluginA(QObject *parent) : QObject(parent)
{

}

void PluginA::show_pluginA()
{
    qDebug()<<"This is a plug-in A";
}

void PluginA::recMsgFromManager(PluginMetaData metaData)
{
    qDebug()<<"plug-in unit A Message received:"<<metaData.msg;
}

pluginB.pro

QT += widgets

TEMPLATE = lib          #Indicates that this makefile is a lib makefile
CONFIG += plugin        #The application is a plug-in

TARGET = pluginB        #Plug in name
DESTDIR = ../plugins    # Output directory

HEADERS += \
    pluginB.h

SOURCES += \
    pluginB.cpp

DISTFILES += \
    pluginB.json

pluginB.h

#ifndef PLUGINB_H
#define PLUGINB_H

#include <QObject>
#include <QtPlugin>
#include <QDebug>
#include "../Main/PluginInterface.h"

class PluginB : public QObject,public PluginInterface
{
    Q_OBJECT
    Q_INTERFACES(PluginInterface)
    Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "pluginB.json")
public:
    explicit  PluginB(QObject *parent = nullptr);
    void show_pluginB();

    void recMsgFromManager(PluginMetaData metaData);

signals:
    void sendMsgToManager(PluginMetaData);
};


#endif // PLUGINB_H

pluginB.cpp

#include "pluginB.h"

PluginB::PluginB(QObject *parent) : QObject(parent)
{

}

void PluginB::show_pluginB()
{
    qDebug()<<"This is a plug-in B";
}

void PluginB::recMsgFromManager(PluginMetaData metaData)
{
    qDebug()<<"plug-in unit B Message received:"<<metaData.msg;
}

3, Create plug-in manager file

Create the PluginManager class in the Main project of the Main program

#ifndef PLUGINMANAGER_H
#define PLUGINMANAGER_H

#include "PluginInterface.h"
#include <QObject>
#include <QPluginLoader>
#include <QVariant>

class PluginManager : public QObject
{
    Q_OBJECT
public:
    explicit PluginManager(QObject *parent = nullptr);
    ~PluginManager();

    static PluginManager *instance(){
         if(m_instance==nullptr)
             m_instance=new PluginManager();
         return m_instance;
     }

    //Scan plug-in metadata in JSON file
    void scanMetaData(const QString &filepath);

     //Load all plug-ins
     void loadAllPlugins();

     //Load one of the plug-ins
     void loadPlugin(const QString &filepath);

     //Uninstall all plug-ins
     void unloadAllPlugins();

     //Uninstall a plug-in
     void unloadPlugin(const QString &filepath);

     //Get all plug-in names
     QList<QVariant> allPluginsName();

     //Get all plug-ins
     QList<QPluginLoader *> allPlugins();

     //Get a plug-in name
     QVariant getPluginName(QPluginLoader *loader);

     //Get plug-ins by name
     QPluginLoader* getPlugin(const QString &name);

public slots:
     void recMsgFromPlugin(PluginMetaData);


private:
     static PluginManager *m_instance;

     class PluginsManagerPrivate;
     PluginsManagerPrivate *managerPrivate;
};

#endif // PLUGINMANAGER_H

#include "PluginManager.h"
#include <QDir>
#include <QCoreApplication>
#include <QJsonArray>
#include <QDebug>

PluginManager* PluginManager::m_instance=nullptr;

class PluginManager::PluginsManagerPrivate
{
public:
    PluginsManagerPrivate()
    {
        m_names.clear();
        m_versions.clear();
        m_dependencies.clear();
        m_loaders.clear();
    }
    ~PluginsManagerPrivate(){}

    QHash<QString, QVariant> m_names;               //Plug in path -- plug in name
    QHash<QString, QVariant> m_versions;            //Plug in path -- plug in version
    QHash<QString, QVariantList> m_dependencies;    //Additional plug-in paths -- other plug-ins
    QHash<QString, QPluginLoader *> m_loaders;      //Plug in path -- QPluginLoader instance

    bool check(const QString &filepath)             //Plug in dependency detection
    {
        bool status = true;

        foreach (QVariant item, m_dependencies.value(filepath)) {
            QVariantMap map = item.toMap();
            // Dependent plug-in name, version and path
            QVariant name = map.value("name");
            QVariant version = map.value("version");
            QString path = m_names.key(name);

            /********** Check whether the plug-in depends on other plug-ins**********/
            // Check the plug-in name first
            if (!m_names.values().contains(name)) {
                qDebug() << Q_FUNC_INFO << "  Missing dependency:" << name.toString() << "for plugin" << path;
                status = false;
                continue;
            }

            // Check the plug-in version again
            if (m_versions.value(path) != version) {
                qDebug() << Q_FUNC_INFO << "    Version mismatch:" << name.toString() << "version"
                         << m_versions.value(m_names.key(name)).toString() << "but" << version.toString() << "required for plugin" << path;
                status = false;
                continue;
            }

            // Then, check whether the dependent plug-in also depends on another plug-in
            if (!check(path)) {
                qDebug() << Q_FUNC_INFO << "Corrupted dependency:" << name.toString() << "for plugin" << path;
                status = false;
                continue;
            }
        }

        return status;
    }

};


PluginManager::PluginManager(QObject *parent) : QObject(parent)
{
    managerPrivate = new PluginsManagerPrivate;
}
PluginManager::~PluginManager()
{
    delete managerPrivate;
}

void PluginManager::loadAllPlugins()
{
    QDir pluginsDir(qApp->applicationDirPath());    //pluginsDir: "../build-xxx-debug/debug"
    if(pluginsDir.dirName().toLower() == "debug" ||
            pluginsDir.dirName().toLower() == "release") {
        pluginsDir.cdUp();  //pluginsDir: "../build-xxx-debug"
        pluginsDir.cdUp();  //pluginsDir: "../"
    }
    pluginsDir.cd("plugins");

    QFileInfoList pluginsInfo = pluginsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);

    //Initialize metadata in plug-in
    for(QFileInfo fileinfo : pluginsInfo){
        //qDebug()<<"loadAllPlugins:"<<fileinfo.absoluteFilePath();
        scanMetaData(fileinfo.absoluteFilePath());
    }

    //Loading plug-ins
    for(QFileInfo fileinfo : pluginsInfo)
        loadPlugin(fileinfo.absoluteFilePath());
}

void PluginManager::scanMetaData(const QString &filepath)
{
    //Judge whether it is a library (suffix validity)
    if(!QLibrary::isLibrary(filepath))
        return ;

    //Acquiring Metadata 
    QPluginLoader *loader = new QPluginLoader(filepath);

    //qDebug()<<loader->metaData().keys();
    QJsonObject json = loader->metaData().value("MetaData").toObject();
//    for(int i=0; i<json.keys().size(); ++i) {
//        qDebug()<<json.keys().at(i)<< " : "<<json.value(json.keys().at(i));
//    }

    managerPrivate->m_names.insert(filepath, json.value("name").toVariant());
    managerPrivate->m_versions.insert(filepath, json.value("version").toVariant());
    managerPrivate->m_dependencies.insert(filepath, json.value("dependencies").toArray().toVariantList());

    delete loader;
    loader = nullptr;
}

void PluginManager::loadPlugin(const QString &filepath)
{
    if(!QLibrary::isLibrary(filepath))
        return;

    //Detection dependency
    if(!managerPrivate->check(filepath))
        return;

    //Loading plug-ins
    QPluginLoader *loader = new QPluginLoader(filepath);
    if(loader->load()) {
        PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());
        if(plugin) {
            managerPrivate->m_loaders.insert(filepath, loader);
            connect(loader->instance(),SIGNAL(sendMsgToManager(PluginMetaData)),
                    this,SLOT(recMsgFromPlugin(PluginMetaData)));
        }else {
            delete loader;
            loader = nullptr;
        }
    }else{
        qDebug()<<"loadPlugin:"<<filepath<<loader->errorString();
    }
}

void PluginManager::unloadAllPlugins()
{
    for(QString filepath : managerPrivate->m_loaders.keys())
        unloadPlugin(filepath);
}

void PluginManager::unloadPlugin(const QString &filepath)
{
    QPluginLoader *loader = managerPrivate->m_loaders.value(filepath);
    //Uninstall the plug-in and remove it from the internal data structure
    if(loader->unload()) {
        managerPrivate->m_loaders.remove(filepath);
        delete loader;
        loader = nullptr;
    }
}

QList<QPluginLoader *> PluginManager::allPlugins()
{
    return managerPrivate->m_loaders.values();
}

QList<QVariant> PluginManager::allPluginsName()
{
    return managerPrivate->m_names.values();
}

QVariant PluginManager::getPluginName(QPluginLoader *loader)
{
    if(loader)
        return managerPrivate->m_names.value(managerPrivate->m_loaders.key(loader));
    else
        return "";
}

QPluginLoader *PluginManager::getPlugin(const QString &name)
{
    return managerPrivate->m_loaders.value(managerPrivate->m_names.key(name));
}

void PluginManager::recMsgFromPlugin(PluginMetaData metaData)
{
    auto loader = getPlugin(metaData.dest); //Target plug-in
    if(loader) {
        auto interface = qobject_cast<PluginInterface*>(loader->instance());
        if(interface) {
            interface->recMsgFromManager(metaData); //Forward to the corresponding plug-in
        }
    }
}

4, Use of plug-in manager

If you compile and run directly, the plug-ins will be generated in the plugins folder without moving. The program loading plug-ins will directly load the plug-ins in this folder

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    PluginManager::instance()->loadAllPlugins();	//Load all plug-ins
    //qDebug()<<"allPluginsName: "<<PluginManager::instance()->allPluginsName();

    QPluginLoader *loader1 = PluginManager::instance()->getPlugin("pluginA");
    if(loader1) {
        PluginInterface *pluginA = dynamic_cast<PluginInterface*>(loader1->instance());
        if(pluginA) {
            PluginMetaData m;
            m.dest = "pluginB";
            m.from = "pluginA";
            m.msg = "plug-in unit A Send to plugin B News of";
            pluginA->sendMsgToManager(m);
        }
    }

    QPluginLoader *loader2 = PluginManager::instance()->getPlugin("pluginB");
    if(loader2) {
        PluginInterface *pluginB = dynamic_cast<PluginInterface*>(loader2->instance());
        if(pluginB) {
            PluginMetaData m;
            m.dest = "pluginA";
            m.from = "pluginB";
            m.msg = "plug-in unit B Send to plugin A News of";
            pluginB->sendMsgToManager(m);
        }
    }

}

Topics: Qt