乐于分享
好东西不私藏

工控软件架构设计(一):为什么你的 Qt 项目越做越烂?

工控软件架构设计(一):为什么你的 Qt 项目越做越烂?

前言

很多Qt工控开发者都有过这样的经历:项目启动时只有一个主窗口。刚开始需求很简单:串口采集数据、显示实时曲线、保存历史记录、控制设备启停。于是大家都会选择最直接的写法:

MainWindow├── SerialPort├── TcpClient├── Camera├── Database├── ChartWidget├── VisionAlgorithm└── ConfigManager

所有功能全部塞进 MainWindow。前期开发速度飞快——一个月完成Demo,三个月完成上线。半年以后……灾难开始了。


单体架构为什么会失控?

曾经接手过一个工业视觉项目,主程序代码量 MainWindow.cpp 达到 16874 行,整个软件只有一个入口,所有模块都直接互相调用(如 CameraWidget → VisionManager → DatabaseManager → SerialManager → AlarmWidget)。看起来方便,实际上形成了蜘蛛网式依赖,任何一个模块修改都牵一发而动全身。

问题一:修改一个Bug引发十个Bug

例如修改相机采集逻辑 CameraManager::capture(),结果视觉检测异常、数据库记录错误、UI刷新异常全来了——因为所有模块直接依赖,根本无法确定影响范围。

问题二:多人开发频繁冲突

项目5个开发人员,每天提交 MainWindow.cpp / .h,Git冲突成为日常,甚至出现 解决冲突时间 > 开发时间

问题三:新增功能成本越来越高

客户提出新增AI缺陷检测模块,原本以为增加一个模块即可,结果发现需要修改主窗口、数据库、通信模块、参数配置、报警模块等 40+ 文件,风险极高。

问题四:长期运行不稳定

工业软件要求 7×24小时运行(连续一周、一月、半年不能崩溃),而单体程序最容易出现内存泄漏、资源未释放、线程失控、句柄耗尽——因为所有模块混在一起,无法隔离问题。


工控软件真正需要什么架构?

工业软件与互联网软件不同:互联网追求高并发、快速迭代;工业软件要求 稳定第一、可扩展第二、性能第三。因此优秀工控软件必须满足:

  • 可插拔:新增功能不修改主程序,直接增加模块。

  • 可隔离:某模块异常不影响其它模块。

  • 易维护:开发5年后依然有人敢改代码。

  • 支持多人协作:算法/设备/前端工程师各司其职,互不影响。


工业级插件架构设计

目前很多工业软件(海康VisionMaster、大华SmartPSS、LabVIEW、Qt Creator)底层都采用插件化架构:

┌─────────────────────┐      主程序Core      └─────────┬───────────┘           ┌────────┴────────┐   Plugin Manager  └────────┬────────┘           ┌────────┼────────┐                                  通信插件  算法插件  UI插件Serial   Vision   Report                  DLL      DLL      DLL

主程序只负责插件加载/卸载、消息转发、生命周期管理,绝不包含业务逻辑。


第一层:核心基座 Core

Core 负责插件扫描、注册、加载、卸载、日志管理、配置管理。例如:

class PluginManager {public:    voidloadPlugins();    voidunloadPlugins();};
启动时自动扫描 plugins/ 目录(Camera.dll, Vision.dll, Database.dll, Alarm.dll)并动态加载:
QDir dir("plugins");QPluginLoader loader(path);

第二层:标准接口层

插件之间绝不能直接引用,必须统一接口:

class IPlugin {public:    virtual QString name() = 0;    virtualboolinitialize() = 0;    virtualvoidshutdown() = 0;};class CameraPlugin : public QObjectpublic IPlugin { ... };

第三层:消息总线

很多项目失败是因为插件之间仍然互相调用(如 CameraPlugin 直接调用 vision->process())。正确做法是使用消息总线

// 发布MessageBus::publish("ImageCaptured", image);// 订阅MessageBus::subscribe("ImageCaptured", callback);

视觉插件自动接收 void VisionPlugin::onImageCaptured(...)。这样相机不知视觉存在,视觉不知数据库存在,数据库不知报警存在,完全解耦。


设备层插件设计

工控软件最大的复杂度来自设备(PLC、相机、激光器、扫码枪、机械臂、IO板卡)。建议 一个设备一个插件(如 SiemensPLC.dllHikCamera.dll),统一接口:

class IDevice {public:    virtualboolconnect() = 0;    virtualvoiddisconnect() = 0;    virtual QVariant read() = 0;};

未来更换设备无需修改业务代码。

算法层插件设计

视觉项目算法最容易变化(模板匹配 → YOLO → OCR),必须插件化:

Algorithm/  OCR.dll, YOLO.dll, Measure.dll, Defect.dll
统一接口:
classIAlgorithm{public:    virtual Result process(const cv::Mat&) = 0;};

实现热插拔。

UI层插件化

Qt Creator 就是典型案例——每个页面独立插件(Dashboard.dllAlarm.dllReport.dllConfig.dll),加载后动态生成:

QWidget* createWidget();stackWidget->addWidget(pluginWidget);

数据存储层设计

不要让业务模块直接访问数据库(错误:VisionPlugin 内写 QSqlQuery)。正确做法是统一 DataService 接口:

classIDataService{ virtual void saveResult(); };

数据库插件(SQLite/MySQL/PostgreSQL/InfluxDB)负责实现,切换无需修改业务代码。

线程架构设计

工业软件第二大坑是线程乱飞(常见 30+ QThread 无人管理,导致内存泄漏、死锁、崩溃)。推荐使用 QThreadPool 统一管理,任务通过 QRunnable 提交执行。

日志系统设计

必须具备可追溯性,统一日志中心记录 时间、线程、模块、级别、内容,例如:

2026-06-18 [Vision] OCR Failed

快速定位问题。

配置中心设计

不要到处使用 QSettings,统一 ConfigManager 支持 JSON/YAML/XML/数据库,所有模块统一读取。

一个成熟工控架构长什么样?

Application├── Core│   ├── PluginManager│   ├── MessageBus│   ├── LogManager│   └── ConfigManager├── Devices│   ├── PLC│   ├── Camera│   └── IO├── Algorithms│   ├── OCR│   ├── AI│   └── Measure├── UI│   ├── Dashboard│   ├── Alarm│   └── Report└── Data    ├── SQLite    ├── MySQL    └── InfluxDB

所有模块独立开发、独立测试、独立部署、独立升级


总结

很多Qt工控项目后期维护成本越来越高,不是因为Qt不好或代码能力不足,而是从第一天就选错了架构。单体架构初期虽快,但终会演变成无人敢改的“巨石系统”。对于需要长期运行、持续迭代的工业软件,更合理的选择是工业级插件化架构:

  • Core 基座负责系统管理

  • Plugin 实现业务功能

  • MessageBus 实现模块解耦

  • Interface 规范模块边界

  • ThreadPool 管理并发任务

  • LogCenter 保证问题可追踪

这样设计的软件,不仅能满足 7×24 小时稳定运行,也能在未来几年持续扩展,而非新增一个功能就推翻重构。优秀的工控软件从来不是功能堆出来的,而是架构设计出来的。