
所有模块全部编译到一个 EXE。于是:
加一个算法,要重新编译整个工程;
一个客户需要 OCR,另一个不需要;
一个客户使用海康相机,另一个使用 Basler;
后续增加 AI 检测……
代码越来越乱。这时候,Plugin(插件) 就成为大型 Qt 软件最重要的架构之一。今天,我们就彻底讲透 Qt Plugin。
为什么需要插件?
先来看一个最经典的工业软件 —— Vision System,它通常包含:
Camera(相机)
Algorithm(算法)
Communication(通信)
Database(数据库)
Robot(机器人)
PLC
Script(脚本)
如果全部写死,Application 会直接依赖一堆具体实现:

带来的问题:
新增一个相机:重新编译整个工程
新增一个算法:重新编译整个工程
客户只买 OCR:仍然需要发布整个软件,无法按需裁剪
显然不合理。所以大型软件都会变成这种结构:

所有模块全部动态加载,主程序只依赖接口。
什么是 Qt Plugin?
Qt Plugin 本质上是:
Windows:DLL
Linux:so
Mac:dylib
但 Qt 在原生动态库之上封装了一层:
QObject + Meta Object + Interface + Plugin Loader
所以开发起来非常舒服。Qt 提供了 QPluginLoader,负责:
加载 DLL/so/dylib
创建插件对象
转换接口
调用函数
整个过程几乎不用写任何平台相关的代码。
Qt Plugin 架构概览

插件管理器负责:
扫描插件目录
加载插件
创建对象
生命周期管理
Plugin 的核心组成
Qt 插件开发只需四步:
定义接口
实现插件
导出插件
动态加载
下面逐一详解。
第一步:定义接口(以相机插件为例)
class ICamera{public:virtual ~ICamera() {}virtualboolopen() = 0;virtualvoidclose() = 0;virtual QImage grab() = 0;};
注意:接口类千万不要继承 QObject,保持纯虚类即可。
然后声明接口的 IID(唯一标识符):
#define CameraInterface_iid "com.demo.ICamera"Q_DECLARE_INTERFACE(ICamera, CameraInterface_iid)
这里 IID 就是插件的唯一 ID,类似 GUID。
第二步:实现插件
例如 HikCameraPlugin:
class HikCameraPlugin :public QObject,public ICamera{Q_OBJECTQ_PLUGIN_METADATA(IID CameraInterface_iid)Q_INTERFACES(ICamera)public:bool open() override;voidclose() override;QImage grab() override;};
关键点:
Q_PLUGIN_METADATA:告诉 Qt 这是一个插件,并指定 IID。Q_INTERFACES:声明实现了哪个接口。
两者缺一不可,否则 Qt 无法正确识别和转换插件。
第三步:编译为动态库
生成的文件放在如下目录结构中(这是 Qt Creator 官方推荐的布局):
App│├── plugins│ ├── camera│ │ ├── HikCamera.dll│ │ ├── Basler.dll│ │ └── MV.dll│ ├── ai│ ├── robot│ ├── protocol│ ├── script│ └── database│└── app.exe
第四步:加载插件
核心代码极其简洁:
QPluginLoader loader(path);QObject *obj = loader.instance();if (obj) {auto camera = qobject_cast<ICamera*>(obj);if (camera) {camera->open();}}
整个过程只需几行代码,Qt 帮我们完成了:
加载动态库(
LoadLibrary/dlopen)创建
QObject实例通过元对象系统进行接口转换
返回插件对象
QPluginLoader 工作流程

全部由 Qt 内部完成,无需手动处理平台 API。
批量扫描插件
实际项目中,不应写死插件路径,而是自动扫描目录:
QDir dir("plugins/camera");QStringList files = dir.entryList(QDir::Files);for (const auto& file : files) {QPluginLoader loader(dir.absoluteFilePath(file));QObject* obj = loader.instance();// 处理插件...}
这样,只要放入新的 DLL,程序就会自动发现并加载,真正实现 零修改扩展。
Plugin Metadata(元数据)
Qt 插件可以携带 JSON 格式的元数据:
Q_PLUGIN_METADATA(IID CameraInterface_iidFILE "camera.json")
camera.json 示例:{"Name": "Hik Camera","Version": "2.1","Vendor": "Demo","Type": "Camera"}
QJsonObject obj = loader.metaData()["MetaData"].toObject();QString name = obj["Name"].toString();
元数据非常适合用于插件管理器,可获取:
插件名称
版本号
作者/供应商
类别
功能描述
一个完整的插件管理器
实际项目中通常会封装一个 PluginManager:
class PluginManager{public:voidloadAll();QList<ICamera*> cameras()const;QList<IAlgorithm*> algorithms()const;QList<IDatabase*> databases()const;private:QList<QPluginLoader*> loaders;};
Application↓PluginManager↓扫描插件目录↓分类插件(根据 IID)↓注册接口↓业务模块调用
业务层完全不知道 DLL、LoadLibrary、QPluginLoader 等细节,真正实现解耦。
多种插件统一管理
推荐将所有插件按功能分类存放:
plugins├── Camera├── OCR├── AI├── PLC├── Robot├── Database├── Logger└── Protocol
IPlugin,让所有插件都继承它:class IPlugin{public:virtual QString name() const = 0;virtual QString version() const = 0;virtualboolinitialize() = 0;virtualvoidrelease() = 0;};// 相机插件接口class ICamera : public IPlugin { /* ... */ };// 算法插件接口class IAlgorithm : public IPlugin { /* ... */ };
这样所有插件拥有统一的生命周期管理。
插件之间如何通信?
这是一个常见的踩坑点。不要让插件之间直接调用,例如:
Camera Plugin↓ 直接调用OCR Plugin
插件之间互相依赖,很快就会形成循环依赖(A → B → C → A),导致代码难以维护。
推荐方案:通过 Plugin Manager + 事件总线
方式一:Qt 信号槽
connect(camera, &ICamera::newFrame,algorithm, &IAlgorithm::process);
方式二:消息总线(发布/订阅)
MessageBus::publish("newFrame", frameData);// 订阅者收到消息MessageBus::subscribe("newFrame", [](const QVariant& data){ ... });
插件之间互相不知道对方存在,实现完全解耦。这是大型软件最经典的设计模式。
插件版本兼容
最怕主程序升级后,旧插件崩溃。解决方案:
1. 接口增加版本号
virtual QString version() const = 0;if (plugin->version() != "3.0") {// 拒绝加载return;}
2. 通过元数据检查
{"Version": "3.2"}
加载时读取元数据中的版本,不匹配则跳过。
热插拔插件
Qt 插件支持运行时卸载、替换 DLL、重新加载:
loader.unload(); // 卸载// 复制新 DLL 到目录loader.load(); // 重新加载QObject* obj = loader.instance(); // 创建新对象
这在工业软件升级时非常方便。
⚠️ 注意:卸载前必须释放所有插件对象,并确保没有任何线程仍在执行插件代码,否则
unload()会失败甚至导致崩溃。
Plugin 最佳实践
| 接口保持纯虚类 | |
| 插件只依赖接口 | |
| 使用 JSON 保存元数据 | |
| 所有插件统一生命周期 | |
| 引入 PluginManager | |
| 使用事件总线或信号槽通信 | |
| 做好版本兼容检查 | |
| 保持接口向后兼容 |
总结
Qt Plugin 并不仅仅是一个动态库加载工具,它更是一种面向扩展的架构思想。
当你的项目从几万行代码增长到几十万甚至上百万行时,插件化能够帮助你解决以下问题:
按需部署:不同客户部署不同功能,无需重新编译整个程序。
降低耦合:主程序只依赖接口,不依赖具体实现。
便于扩展:新增功能通常只需增加一个插件即可。
独立升级:插件可以单独迭代、修复和发布。
团队协作:不同团队可以并行开发不同插件,提高研发效率。
对于工业自动化、机器视觉、医疗设备、CAD、GIS、IDE 等大型 Qt 软件而言,插件系统几乎都是核心基础设施。掌握 QPluginLoader、接口设计、插件管理器以及版本兼容策略,能够让您的 Qt 项目具备长期演进和持续扩展的能力。
夜雨聆风