赞
踩
代码在Qt5.12.11和Qt6.1.1的linux版下测试ok。
插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现/扩展应用软件平台不具备的功能的程序。
具体的体现形式:
linux: .so文件(动态库),.a文件(静态库)
windows: *.dll文件(动态库), *.lib文件(静态库)
A、定义一个接口集(只有纯虚函数的类),用来与插件交流。
B、用宏Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统。
C、应用程序中用QPluginLoader来加载插件。
D、用宏qobject_cast()来判断一个插件是否实现了接口。
E、调用插件的功能。
创建一个插件的步骤如下:
A、声明插件类,插件类继承自QObject和插件实现的接口。
B、用宏Q_INTERFACES()将插件接口告诉Qt元对象系统。
C、用宏Q_EXPORT_PLUGIN2()导出插件类。
D、用适当的.pro文件构建插件。
在加载插件前, QCoreApplication对象必须被初始化。
使用QtCreator创建子目录工程(Subdirs Project),命名为PluginDemoAndTest
;
其PluginDemoAndTest.pro文件如下:
TEMPLATE = subdirs
SUBDIRS += \
PluginDemo \
PluginTest
然后分别添加插件子工程,应用子工程。
在PluginDemoAndTest
上右键选择New Subproject
,选择创建为空的Qt工程,取名PluginDemo
,其PluginDemo.pro文件如下:
QT += core TEMPLATE = lib TARGET = $$qtLibraryTarget(PluginDemo) CONFIG += c++11 plugin TARGET = PluginDemo DESTDIR = ../plugins # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ plugindemo.cpp # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target HEADERS += \ plugindemo.h \ plugininterface.h
创建接口文件plugininterface.h如下:
#ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include <QString> #include <QtPlugin> class PluginInterface { public: virtual ~PluginInterface() {} //避免编译器抱怨 virtual QStringList interfaceList() = 0; virtual QString action(QString& interface) = 0; }; QT_BEGIN_NAMESPACE #define PluginInterface_iid "org.csm.embed.plugin.pluginInterface.1.0" Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid) QT_END_NAMESPACE #endif // PLUGININTERFACE_H
包含了提供接口查询的interfaceList()方法,以及根据调用接口实现功能的action()方法,使用纯虚方法,而析构函数的声明是为了满足一些编译器的要求。
然后创建PluginInterface的实现:
//plugindemo.h #ifndef PLUGINDEMO_H #define PLUGINDEMO_H #include <QObject> #include "plugininterface.h" class PluginDemo : public QObject, PluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.csm.embed.plugin.pluginInterface.1.0" FILE "plugindemo.json") Q_INTERFACES(PluginInterface) public: explicit PluginDemo(); QStringList interfaceList(); QString action(QString& interface); private: QStringList m_ifList; }; #endif // PLUGINDEMO_H
这里是一个PluginInterface的具体实现,其采用多继承方式,切第一个父类必须为QObject类,然后是接口。(如果其接口已经是QObject的子类,这段话作废)
//plugindemo.cpp #include "plugindemo.h" #include <QDebug> PluginDemo::PluginDemo() { m_ifList.append("interface1"); m_ifList.append("interface2"); } QStringList PluginDemo::interfaceList() { return m_ifList; } QString PluginDemo::action(QString &interface) { QString actionName; if(m_ifList.contains(interface)) { actionName = interface + " done..."; } else { actionName = interface + " is not exists"; } qDebug() << actionName; return actionName; }
通过interfaceList()返回接口列表,而具体的action则根据interface来动作,即返回/打印字符串。
Plugin工程至此结束。
接下来创建使用/测试PluginDemo的应用工程, 其PluginDemo.pro文件如下,其关键点是需要引入plugininterface.h 头文件
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ widget.cpp HEADERS += \ ../PluginDemo/plugininterface.h \ widget.h # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
main.cpp创建widget显示,并进入Qt事件循环机制:
//main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
widget类实现,负责具体的调用实现了PluginInterface接口的PluginDemo类。
//widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QStringList> class QLineEdit; class QLabel; class PluginInterface; class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void loadPlugin(); public slots: void queryInterface(); void doAction(); private: QLabel* ifListInfo; //显示接口列表 QLineEdit* actionName ; //输入接口名称 PluginInterface* interfacePtr; //保存接口指针 QStringList interfaceList; //保存运行结果 }; #endif // WIDGET_H
具体的widget实现了,关键点queryInterface
槽函数中的loadPlugin()
,以及doAction
槽函数中对接口的调用
//widget.cpp #include "widget.h" #include <QGridLayout> #include <QPushButton> #include <QLabel> #include <QLineEdit> #include <QDebug> #include <QDir> #include <QtGlobal> #include <QApplication> #include <QPluginLoader> #include "../PluginDemo/plugininterface.h" Widget::Widget(QWidget *parent) : QWidget(parent) { this->setMinimumSize(600, 400); QGridLayout* layout = new QGridLayout(this); QLabel* ifListName = new QLabel("list: "); ifListInfo = new QLabel(""); QPushButton* queryButton = new QPushButton("inqury"); connect(queryButton, &QPushButton::clicked, this, &Widget::queryInterface); layout->addWidget(ifListName, 0, 0); layout->addWidget(ifListInfo, 0, 1); layout->addWidget(queryButton, 0, 2); //QPushButton QLabel* actionLabel = new QLabel("interface:"); actionName = new QLineEdit(); actionName->setPlaceholderText("input one interface of list above"); QPushButton* actionButton = new QPushButton("action"); connect(actionButton, &QPushButton::clicked, this, &Widget::doAction); layout->addWidget(actionLabel, 1, 0); layout->addWidget(actionName, 1, 1); layout->addWidget(actionButton, 1, 2); doneResult = new QLabel("result"); layout->addWidget(doneResult, 2, 0, 1, 3, Qt::AlignCenter); interfacePtr = Q_NULLPTR; } Widget::~Widget() { } void Widget::loadPlugin() { bool bRet = true; //路径根据系统差异来具体实现 QDir pluginDir(qApp->applicationDirPath()); #if defined (Q_OS_WIN) if (pluginDir.dirName().toLower() == "debug" || pluginDir.dirName().toLower() == "release") { pluginDir.cdUp(); pluginDir.cdUp(); } #elif defined Q_OS_LINUX //Q_OS_WIN pluginDir.cdUp(); #endif //Q_OS_LINUX pluginDir.cd("plugins"); foreach(QString fileName, pluginDir.entryList(QDir::Files)) { //获取插件的具体路径 QString file = pluginDir.absoluteFilePath(fileName); //实例化QPluginLoader类 QPluginLoader pluginLoader(file); //返回插件实例 QObject* plugin = pluginLoader.instance(); if (plugin) { //注意此处是插件类名,而不是接口类名称哦 QString plugName = plugin->metaObject()->className(); if (plugName == "PluginDemo") { interfacePtr = qobject_cast<PluginInterface*>(plugin); interfaceList = interfacePtr->interfaceList(); break; } } else { qDebug() << pluginLoader.errorString(); } } } void Widget::queryInterface() { //qDebug() << "query"; loadPlugin(); QString interfaceList; if (interfacePtr != Q_NULLPTR) { foreach(auto interfaceName, interfacePtr->interfaceList()) { interfaceList.append(interfaceName); interfaceList.append(", "); } if (!interfaceList.isEmpty()) interfaceList.remove(interfaceList.length() - 2, 2); } ifListInfo->setText(interfaceList); } void Widget::doAction() { //qDebug() << "doAction"; QString action = actionName->text(); QString doneAction = interfacePtr->action(action); doneResult->setText(doneAction); }
实际运行结果:
Qt应用程序将会自动感知可用的插件,因为插件都被存储在标准的子目录当中。因此应用程序不需要任何查找或者加载插件的代码。
在开发过程中,插件的目录是QTDIR/plugins(QTDIR是Qt的安装目录),每个类型的插件放在相应类型的目录下面。如果想要应用程序使用插件,但不想用标准的插件存放路径,可以在应用程序的安装过程中指定要使用的插件的路径,可以使用QSettings,保存插件路径,在应用程序运行时读取配置文件。应用程序可以通过QCoreApplication::addLibraryPath()函数将指定的插件路径加载到应用程序中。
使插件可加载的一种方法是在应用程序所在目录创建一个子目录,用于存放插件。如果要发布和Qt一起发布的插件(存放在plugins目录)中的任何插件,必须拷贝plugins目录下的插件子目录到应用程序的根目录下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。