乐于分享
好东西不私藏

14|插件工程

14|插件工程

14|插件工程

上一节我们介绍了动态库,很多开发者学完之后都会产生一个疑问:

Qt 插件是不是就是一个 DLL?

答案是:

插件本质上是动态库,但并不是所有动态库都是插件。

动态库只是代码的载体,而插件则是在动态库的基础上,增加了一套统一的接口规范、元数据描述以及运行时加载机制。

在 Qt 中,插件机制已经十分成熟,从数据库驱动、图像格式支持,到平台适配(QPA)、Designer 插件,几乎全部都是基于 Qt Plugin 实现的。

本节我们就来学习 Qt6 插件工程的基本原理与开发方式。


什么是插件?

简单来说,插件就是:

主程序不知道插件的具体实现,只知道插件遵循某种接口规范。

例如:

                App                 │      ┌──────────┼──────────┐      │          │          │      ▼          ▼          ▼ CameraPlugin  RobotPlugin  PlcPlugin

主程序只负责:

  • • 搜索插件
  • • 加载插件
  • • 获取接口
  • • 调用功能

至于具体实现是谁完成的,主程序完全不用关心。

这就是插件架构最大的优势:

扩展,而无需修改主程序。


为什么使用插件?

假设我们开发一套工业软件。

支持:

西门子 PLC欧姆龙 PLC三菱 PLC倍福 PLC

如果全部写进主程序:

App.exe

以后每增加一种 PLC:

都需要:

重新编译;

重新发布;

重新测试。

这显然不现实。

插件方案:

plugins/    Siemens.dll    Omron.dll    Mitsubishi.dll    Beckhoff.dll

以后增加:

ABB.dll

只需要:

复制到:

plugins/

无需修改主程序。


Qt 插件由哪些部分组成?

一个标准 Qt 插件通常包括四部分:

InterfacePlugin ClassMetadataPlugin Library

分别承担不同职责。


第一步:定义接口

所有插件都应该遵循同一个接口。

例如:

class IDevice{public:    virtual ~IDevice() = default;virtual QString name() const= 0;virtual bool open()= 0;virtual void close()= 0;};

以后:

所有设备插件都实现:

IDevice

主程序只认识接口。

不认识具体类。


第二步:声明接口

Qt 需要知道:

这个接口可以被插件识别。

因此:

#define DeviceInterface_iid \    "com.company.IDevice"Q_DECLARE_INTERFACE(    IDevice,    DeviceInterface_iid)

这里:

IID

就是:

Interface ID。

类似:

身份证号码。

Qt 通过它判断:

插件是否实现了指定接口。


第三步:实现插件

例如:

class SiemensPlugin    : public QObject,      public IDevice{    Q_OBJECT    Q_PLUGIN_METADATA(        IID DeviceInterface_iid    )    Q_INTERFACES(IDevice)public:    QString name() const override;bool open() override;void close() override;};

这里最重要三个宏。


Q_OBJECT

表示:

启用:

Qt 元对象系统。

插件几乎都需要它。


Q_PLUGIN_METADATA

告诉 Qt:

这是一个插件。

例如:

Q_PLUGIN_METADATA(    IID DeviceInterface_iid)

Qt 会自动生成:

插件元数据。


Q_INTERFACES

告诉 Qt:

当前类实现了:

IDevice

这样:

主程序才能:

qobject_cast<IDevice*>()

成功。


第四步:生成插件

Qt6:

qt_add_plugin(SiemensPlugin)target_sources(SiemensPlugin    PRIVATE        SiemensPlugin.cpp)

编译后:

Windows:

SiemensPlugin.dll

Linux:

libSiemensPlugin.so

插件本质仍然是:

动态库。


主程序如何加载插件?

Qt 提供:

QPluginLoader

例如:

QPluginLoader loader(path);QObject* obj = loader.instance();

如果:

obj == nullptr

说明:

插件加载失败。

如果成功:

IDevice* device =    qobject_cast<IDevice*>(obj);

转换成功。

即可:

device->open();

整个过程:

完全不知道:

插件真实类型。


自动扫描插件目录

实际项目:

不会手动指定:

Siemens.dll

通常:

扫描:

plugins/

例如:

plugins/    Siemens.dll    Omron.dll    ABB.dll

Qt:

QDir dir("plugins");QStringList files =    dir.entryList(QDir::Files);

遍历:

每个 DLL。

全部:

QPluginLoader

加载。

这样:

以后:

新增插件:

无需修改代码。


插件 Metadata

插件不仅包含代码。

还有:

Metadata。

例如:

Q_PLUGIN_METADATA(    IID DeviceInterface_iid    FILE "plugin.json")

对应:

{    "Name": "Siemens",    "Version": "1.0",    "Author": "Qt Team"}

运行时:

Qt 可以读取:

这些信息。

无需真正实例化插件。

非常方便。


Qt 官方插件也是这样工作的

例如:

图片加载:

QImage image("a.webp");

Qt:

自动:

搜索:

imageformats/

里面:

qjpeg.dllqgif.dllqwebp.dllqtiff.dll

每个都是:

插件。

如果:

删除:

qwebp.dll

那么:

Qt:

不能读取:

WebP。

这就是插件机制。


Qt SQL 驱动

数据库驱动:

也是插件。

例如:

sqldrivers/    qsqlite.dll    qmysql.dll    qodbc.dll

代码:

QSqlDatabase::addDatabase("QMYSQL");

Qt:

自动:

加载:

qmysql.dll

无需:

程序员:

自己:

LoadLibrary。


QPA 平台插件

Qt 最核心插件之一。

例如:

Windows:

platforms/    qwindows.dll

Linux:

qxcb.so

macOS:

qcocoa.dylib

Qt:

启动:

第一件事:

就是:

加载:

平台插件。

否则:

程序:

无法创建窗口。

这也是很多人遇到:

Could not load Qt platform plugin "xcb"

错误的原因。


插件工程目录建议

企业项目:

通常:

MyProject├── App├── Interface│   └── IDevice.h├── Plugins│   ├── Siemens│   ├── Omron│   ├── Mitsubishi│   └── Beckhoff├── PluginManager└── CMakeLists.txt

其中:

Interface:

只放:

接口。

Plugin:

只负责实现。

PluginManager:

统一:

扫描;

加载;

卸载;

管理插件生命周期。

这种结构具有良好的可扩展性,也便于多人协作开发。


插件开发建议

Qt 插件系统不仅适用于第三方扩展,也适用于大型项目内部的模块解耦。实际开发中,可以参考以下经验:

  1. 1. 接口保持稳定。 插件与主程序通过接口通信,接口一旦发布,应尽量避免破坏性修改。
  2. 2. 主程序依赖接口,不依赖具体实现。 所有插件都应通过统一接口访问,避免在主程序中判断具体插件类型。
  3. 3. 合理使用元数据。 将插件名称、版本、支持设备类型等信息写入 Metadata,方便插件管理和兼容性检查。
  4. 4. 统一管理插件生命周期。 建议封装 PluginManager,负责扫描目录、加载插件、卸载插件以及异常处理,而不是在业务代码中直接使用 QPluginLoader。
  5. 5. 插件之间保持独立。 一个插件应只完成一种职责,避免插件之间互相依赖,否则会降低整个插件体系的灵活性。

Qt 插件机制是构建可扩展软件架构的重要基础。它不仅广泛应用于 Qt 自身,也适用于工业控制、CAD、IDE、图像处理、仪器仪表等需要按需扩展功能的应用场景。掌握插件工程的设计思想,将使你的 Qt 项目具备更好的可维护性和可扩展性。