本文使用“Qt Creator 6.0.1”和“Qt 6.2.2”完成插件创建及使用,主要有如下步骤:(1)创建子目录项目MyProject;(2)在子目录项目中创建应用程序项目MyApp;(3)在子目录项目中创建插件项目MyPlugin;(4)在项目MyApp中添加接口文件“MyInterfaces.h”;(5)在接口文件“MyInterfaces.h”中添加接口AnimalInterface;(6)在项目MyPlugin中实现接口;(7)在项目MyApp中使用插件。
1. 创建子目录项目MyProject
在Qt Creator的主界面点击“文件”--“新建文件或项目”,在弹出的对话框中选择“其他项目”--“子目录项目”,如下图所示:
点击“Choose...”,指定项目名称(指定为MyProject)、项目创建路径(D:\QtTests),如下图所示:
点击“下一步”,在弹出的对话框中指定Kits(构建套件)(我选择的是“Desktop (x86_windows_msvc2019_pe_64bit) ”)
点击“下一步”
点击“完成& 添加子项目”按钮,完成子目录项目MyProject的创建。
紧接着,Qt Creator会自动弹出“新建子项目”对话框,这就进入了下一个步骤“在子目录项目中创建应用程序MyApp”。
2. 在子目录项目中创建应用程序项目MyApp
在“新建子项目”对话框中选择“Application”--“Qt Widgets Application”
点击“Choose...”,指定项目名称(指定为MyApp)、项目创建路径(D:\QtTests),如下图所示:
点击“下一步”,指定构建系统(Build System)(我指定为qmake),
点击“下一步”,
无需修改,点击“下一步”
无需修改,点击“下一步”
我选择的是“Desktop (x86_windows_msvc2019_pe_64bit) ”,点击“下一步”
点击“完成”按钮,完成应用程序项目MyApp的创建。
3. 在子目录项目中创建插件项目MyPlugin
右键单击子目录项目MyProject,在弹出菜单中点击“新子项目...”
在弹出的对话框中选择“Library”--“C++ Library”
点击“Choose...”按钮,在弹出的对话框中指定名称(MyPlugin)和创建路径(D:\QtTests\MyProject),
点击“下一步”,指定构建系统(Build System)(我指定为qmake),
点击“下一步”,指定类型为“Qt Plugin”
点击“下一步”
点击“下一步”
点击“下一步”
点击“完成”按钮,完成插件项目MyPlugin的创建。
为了使得项目能够正常编译 ,可将函数GenericPlugin::create中的代码“static_assert(false, "You need to implement this function");”暂时修改为“return nullptr;”。
4. 在项目MyApp中添加接口文件“MyInterfaces.h”
右键单击项目MyProject,在弹出菜单中点击“Add New...”
在弹出的对话框中选择“C/C++”--“C/C++ Header File”
点击按钮“Choose...”,在弹出的对话框中指定文件名(“MyInterfaces.h”)、路径(“D:\QtTests\MyProject\MyApp”)
点击“下一步”
点击“完成”按钮,完成接口文件“MyInterfaces.h”的添加。Qt Creator会自动修改文件“MyApp.pro”,将HEADERS修改为:
HEADERS += \
MyInterfaces.h \
mainwindow.h
5. 在接口文件“MyInterfaces.h”中添加接口AnimalInterface
AnimalInterface即动物接口,该接口中就一个方法cry,这里cry意思是叫,大多数动物都会叫。
添加接口AnimalInterface的定义后,文件“MyInterfaces.h”的内容如下:
#ifndef MYINTERFACES_H
#define MYINTERFACES_H
#include <QtPlugin>
class AnimalInterface{
public:
virtual ~AnimalInterface() = default;
virtual int cry() = 0;
};
QT_BEGIN_NAMESPACE
#define AnimalInterface_iid "MyPlugin.AnimalInterface/1.0"
Q_DECLARE_INTERFACE(AnimalInterface, AnimalInterface_iid)
QT_END_NAMESPACE
#endif // MYINTERFACES_H
类AnimalInterface声明了一个纯虚函数cry(当然也可以声明更多的纯虚函数)。
该类还有一个虚拟析构函数。接口类通常不需要这样的析构函数(因为通过指向接口的指针删除实现接口的对象没有什么意义),但一些编译器会对声明虚拟函数但没有虚拟析构函数的类发出警告。我们提供析构函数来让这些编译器满意(即不发出警告)。(这段话来自参考资料[6],原文是: The class also has a virtual destructor. Interface classes usually don't need such a destructor (because it would make little sense to delete the object that implements the interface through a pointer to the interface), but some compilers emit a warning for classes that declare virtual functions but no virtual destructor. We provide the destructor to keep these compilers happy.)
QT_BEGIN_NAMESPACE的主要作用是定义一个命名空间,以避免不同库中的标识符冲突。通过使用QT_BEGIN_NAMESPACE和QT_END_NAMESPACE宏,Qt框架将所有的类和函数封装在一个特定的作用域内,从而防止它们与全局标识符或其他库中的标识符发生冲突。
为了在运行时可以查询某插件是否实现了给定的接口,我们必须使用Q_DECLARE_INTERFACE()宏。该宏的第一个参数是接口的名称;第二个参数是一个以唯一方式标识接口的字符串。按照惯例,我们使用“Java包名”语法来标识接口。若在后期更改接口,则必须使用不同的字符串来标识新接口;否则,应用程序可能会崩溃。因此,如上述代码所示,在字符串中加入版本号是一个好主意。(这段话来自参考资料[6],原文是:To make it possible to query at run-time whether a plugin implements a given interface, we must use the Q_DECLARE_INTERFACE() macro. The first argument is the name of the interface. The second argument is a string identifying the interface in a unique way. By convention, we use a "Java package name" syntax to identify interfaces. If we later change the interfaces, we must use a different string to identify the new interface; otherwise, the application might crash. It is therefore a good idea to include a version number in the string, as we did above.)
6. 在项目MyPlugin中实现接口
为了便于在项目MyPlugin的代码中找到接口定义,在文件“MyPlugin.pro”添加如下代码:
INCLUDEPATH += ../MyApp
DESTDIR = ../MyApp/Plugins
INCLUDEPATH指定项目要用到的头文件路径,从而避免在#include时需要提供完整的文件路径;DESTDIR宏用于指定生成的可执行文件(或库文件)的输出目录。
添加接口的实现类。这里,我们创建两个类:一个类是狗Dog,另一个类是猫Cat。这两个类都是接口AnimalInterface的实现类,在实现方法cry(叫)时,发出的声音不同,狗“汪汪”叫,猫“喵喵”叫。
(1)添加Dog类
右键单击项目“MyPlugin”,在弹出的菜单中点击“Add New...”
在弹出的对话框中,选择“C/C++”--“C++ Class”
点击按钮“Choose...”,在弹出的对话框中:指定类名(Class name)为Dog,基类(Base class)指定为“<custom>”--“AnimalInterface”,勾选“Include QObject”、“Add Q_OBJECT”。
点击“下一步”,
点击“完成”按钮,完成Dog类的添加。
“Dog.h”的初始代码如下:
#ifndef DOG_H
#define DOG_H
#include <QObject>
class Dog : public AnimalInterface
{
Q_OBJECT
public:
Dog();
};
#endif // DOG_H
向其中添加继承QObject及接口AnimalInterface的实现方法后,“Dog.h”的代码如下:
#ifndef DOG_H
#define DOG_H
#include <QObject>
#include "MyInterfaces.h" // 添加
class Dog : public QObject, // 添加
public AnimalInterface
{
Q_OBJECT
Q_INTERFACES(AnimalInterface) // 添加
public:
Dog();
virtual int cry(); // 添加
};
#endif // DOG_H
在“Dog.cpp”中,添加cry的实现代码,如下:
#include <QMessageBox>
int Dog::cry()
{
QMessageBox::information(nullptr, "Dog", "WangWang!");
return 1;
}
为了使用QMessageBox,需要将文件“MyPlugin.pro”中的“QT += gui”修改为“QT += gui widgets”。
(2)添加Cat类
添加Cat类的步骤与添加Dog类的步骤基本相同。
“Cat.h”的代码如下:
#ifndef CAT_H
#define CAT_H
#include <QObject>
#include "MyInterfaces.h" // 添加
class Cat : public QObject, // 添加
public AnimalInterface
{
Q_OBJECT
Q_INTERFACES(AnimalInterface) // 添加
public:
explicit Cat(QObject *parent = nullptr);
virtual int cry(); // 添加
};
#endif // CAT_H
在“Cat.cpp”中,添加cry的实现代码,如下:
#include <QMessageBox>
int Cat::cry()
{
QMessageBox::information(nullptr, "Cat", "MiaoMiao!");
return 1;
}
(3)修改函数GenericPlugin::create的实现代码
代码修改为:
#include "dog.h"
#include "cat.h"
QObject *GenericPlugin::create(const QString &name, const QString &spec)
{
//static_assert(false, "You need to implement this function");
if(AnimalInterface_iid != name)
return nullptr;
if(spec == "Dog")
return new Dog;
else if(spec == "Cat")
return new Cat;
return nullptr;
}
7. 在项目MyApp中使用插件
为了简化处理,直接在main函数中使用插件。
对“main.cpp”进行修改,最终代码如下:
#include "mainwindow.h"
#include <QApplication>
#include "MyInterfaces.h" // 添加
#include <QPluginLoader> // 添加
#include <QDir> // 添加
#include <QList> // 添加
#include <QGenericPlugin> // 添加
#include <QMessageBox> // 添加
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
/// 添加,开始
QDir pluginDir; // 插件所在的目录
pluginDir = QDir(QCoreApplication::applicationDirPath());
QString strDir = pluginDir.absolutePath();
pluginDir.cd("../Plugins");
QList<QString> pluginFileNames; // 插件文件名
auto entryList = pluginDir.entryList(QDir::Files);
for (const QString &fileName : entryList) {
//使用QPluginLoader加载插件
QPluginLoader loader(pluginDir.absoluteFilePath(fileName));
QObject *plugin = loader.instance();
if (plugin) {
pluginFileNames.append(pluginDir.absoluteFilePath(fileName));
}
}
//将插件实例pluginLoader.instance()转换为QGenericPlugin类型
QGenericPlugin *genericPlugin = nullptr;
QPluginLoader loader(pluginFileNames[0]);
QObject *plugin = loader.instance();
if (plugin) {
genericPlugin = qobject_cast<QGenericPlugin *>(plugin);
}
if (genericPlugin) { // 插件加载成功,可以使用插件
// 使用 QGenericPlugin 的 create 函数来创建对象
// 创建一个狗的实例,并调用函数cry
AnimalInterface *pDog = qobject_cast<AnimalInterface *>
(genericPlugin->create(AnimalInterface_iid, "Dog"));
if(pDog)
pDog->cry();
// 创建一个猫的实例,并调用函数cry
AnimalInterface *pCat = qobject_cast<AnimalInterface *>
(genericPlugin->create(AnimalInterface_iid, "Cat"));
if(pCat)
pCat->cry();
}else{
// 插件加载失败
QMessageBox::information(nullptr, "main", "插件加载失败!");
}
/// 添加,结束
MainWindow w;
w.show();
return a.exec();
}
该程序首先使用QCoreApplication::applicationDirPath()获取可执行文件(“MyApp.exe”)所在的目录,然后进入该目录的上级目录的Plugins目录(因为文件“MyPlugin.pro”中有代码“DESTDIR = ../MyApp/Plugins”),然后获取Plugins目录中的所有文件名并保存在entryList中;然后使用QPluginLoader逐个文件加载,若成功,则将文件名的绝对路径保存到字符串列表pluginFileNames中;这里为了简化,只有一个插件,所以,随后再使用QPluginLoader加载第一个文件;将插件实例pluginLoader.instance()转换为QGenericPlugin类型genericPlugin;最后调用genericPlugin的create函数创建了一个狗的实例、一个猫的实例,并调用了每个实例的cry函数。
该程序运行的结果是先弹出一个狗叫的对话框,再弹出一个猫叫的对话框。
如下图所示:
8. 值得一提的参考资料
参考资料[6]是一个优秀的创建和使用Qt插件的例子,值得大家学习,参考资料[7]是这个例子的源代码网址。
9. 参考资料:
[1]使用QGenericPlugin和QPluginLoader创建和加载插件. 使用QGenericPlugin和QPluginLoader创建和加载插件-CSDN博客 .
[2] QGenericPlugin Class. QGenericPlugin Class | Qt GUI 6.2.13 .
[3] QPluginLoader Class. QPluginLoader Class | Qt Core 6.2.13 .
[4] How to Create Qt Plugins. How to Create Qt Plugins | Qt 6.2 .
[5] Deploying Plugins (部署插件). Deploying Plugins | Qt 6.2 .
[6] Plug & Paint Example. Plug & Paint Example | Qt Widgets 6.2.13 .
[7] plugandpaint源代码. tools « widgets « examples - qt/qtbase.git - Qt Base (Core, Gui, Widgets, Network, ...) .