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