# QtModule **Repository Path**: runinmind/qt-module ## Basic Information - **Project Name**: QtModule - **Description**: No description available - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-20 - **Last Updated**: 2026-09-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # QM Upper Computer Framework 一个基于 **Qt / C++ / qmake** 构建的模块化上位机应用工程。 本项目旨在构建一套可复用、可扩展的上位机基础框架,将串口、TCP、WebSocket、日志、配置、Property、数据库、崩溃处理以及常用 UI 组件等能力进行统一组织,为不同类型的上位机应用提供公共基础设施。 项目采用 **多 App + 源码模块化 + `.pri` 依赖管理** 的工程组织方式。 > 当前阶段不将公共模块编译为 `.lib` / `.dll` / `.a` / `.so`,而是通过 qmake `.pri` 文件将模块源码直接集成到各个 App 中。 --- ## 目录 * [设计目标](#设计目标) * [核心设计思想](#核心设计思想) * [工程结构](#工程结构) * [目录职责](#目录职责) * [App 与 Module 的关系](#app-与-module-的关系) * [`.pri` 模块设计](#pri-模块设计) * [模块依赖关系](#模块依赖关系) * [`.pri` 重复包含问题](#pri-重复包含问题) * [为什么不直接编译公共库](#为什么不直接编译公共库) * [qmake 工程组织](#qmake-工程组织) * [模块开发规范](#模块开发规范) * [命名规范](#命名规范) * [依赖设计原则](#依赖设计原则) * [未来演进](#未来演进) --- # 设计目标 本项目不是针对某一个具体上位机产品,而是希望建立一个可以长期演进的 **Qt 上位机基础工程**。 主要目标: * 支持多个独立上位机 App * 公共代码高度复用 * 模块之间保持低耦合 * 不依赖复杂的第三方构建系统 * 使用 qmake 即可完成工程组织 * 支持 Windows / Linux 等平台 * 方便后续适配国产操作系统、国产硬件及国产软件环境 * 公共模块可以直接以源码形式集成 * App 可以按需选择模块 * 模块之间允许存在多级依赖 * 避免公共模块重复加入工程 * 为未来转换为动态库 / 静态库保留空间 --- # 核心设计思想 项目采用: ```text 多 App ↓ 模块化源码 ↓ .pri 管理模块 ↓ qmake 负责构建 ``` 而不是: ```text 多 App ↓ 公共 DLL / LIB ↓ App 链接公共库 ``` 因此,公共模块本质上是: > **可复用的源码模块,而不是独立的二进制库。** 例如: ```text App ├── property.pri │ └── config.pri │ └── core.pri │ ├── serial.pri │ └── core.pri │ └── websocket.pri └── core.pri ``` 最终 qmake 会将这些模块需要的源码一起加入当前 App 的构建过程。 --- # 工程结构 推荐工程结构: ```text qm/ │ ├── qm.pro │ ├── apps/ │ ├── monitor/ │ │ ├── monitor.pro │ │ ├── main.cpp │ │ └── ... │ │ │ ├── simulator/ │ │ ├── simulator.pro │ │ ├── main.cpp │ │ └── ... │ │ │ └── configurator/ │ ├── configurator.pro │ ├── main.cpp │ └── ... │ ├── modules/ │ ├── core/ │ │ ├── core.h │ │ ├── core.cpp │ │ └── core.pri │ │ │ ├── log/ │ │ ├── log.h │ │ ├── log.cpp │ │ └── log.pri │ │ │ ├── config/ │ │ ├── config.h │ │ ├── config.cpp │ │ └── config.pri │ │ │ ├── property/ │ │ ├── property.h │ │ ├── property.cpp │ │ └── property.pri │ │ │ ├── serial/ │ │ ├── serial.h │ │ ├── serial.cpp │ │ └── serial.pri │ │ │ ├── tcp/ │ │ ├── tcp.h │ │ ├── tcp.cpp │ │ └── tcp.pri │ │ │ ├── websocket/ │ │ ├── websocket.h │ │ ├── websocket.cpp │ │ └── websocket.pri │ │ │ ├── database/ │ │ ├── database.h │ │ ├── database.cpp │ │ └── database.pri │ │ │ ├── crash/ │ │ ├── crash.h │ │ ├── crash.cpp │ │ └── crash.pri │ │ │ └── plugin/ │ ├── plugin.h │ ├── plugin.cpp │ └── plugin.pri │ ├── widgets/ │ ├── property/ │ ├── log/ │ ├── dock/ │ └── chart/ │ ├── thirdparty/ │ ├── ... │ └── ... │ ├── common/ │ ├── platform.pri │ ├── compiler.pri │ └── version.pri │ └── README.md ``` --- # 目录职责 ## `apps/` 存放最终运行的上位机应用。 例如: ```text apps/ ├── monitor/ ├── simulator/ └── configurator/ ``` 每个 App 都是一个独立的可执行程序。 例如: ```text monitor.exe simulator.exe configurator.exe ``` App 负责: * `main.cpp` * 主窗口 * App 特有业务逻辑 * App 特有 UI * 选择需要使用的公共模块 App 不负责实现公共基础能力。 --- # `modules/` 存放可复用的功能模块。 例如: ```text modules/ ├── core/ ├── log/ ├── config/ ├── property/ ├── serial/ ├── tcp/ ├── websocket/ ├── database/ └── crash/ ``` 模块原则: > 一个模块负责一个相对完整的能力。 例如: ```text core ``` 负责基础设施。 ```text log ``` 负责日志。 ```text config ``` 负责配置管理。 ```text property ``` 负责 Property 系统。 ```text serial ``` 负责串口通信。 ```text websocket ``` 负责 WebSocket 通信。 --- # `widgets/` 存放可复用的 Qt UI 组件。 例如: ```text widgets/ ├── property/ ├── log/ ├── dock/ └── chart/ ``` 这里与 `modules/` 有明确区别。 ### modules 主要解决: ```text 功能 业务能力 基础设施 数据处理 通信 配置 ``` ### widgets 主要解决: ```text Qt UI 界面组件 可视化 交互 ``` 例如: ```text modules/property ``` 提供: ```cpp Property PropertyManager PropertyGroup ``` 而: ```text widgets/property ``` 提供: ```cpp PropertyEditor PropertyTree PropertyWidget ``` 两者可以配合使用,但职责不同。 --- # `thirdparty/` 存放第三方依赖。 例如: ```text thirdparty/ ├── ... ├── ... └── ... ``` 第三方库不应该与自己的业务模块混在一起。 原则: ```text thirdparty ↓ modules ↓ apps ``` 尽量避免: ```text thirdparty ↔ thirdparty ``` 以及: ```text modules ↓ apps ↓ modules ``` 形成复杂的反向依赖。 --- # `common/` 存放 qmake 工程级公共配置。 例如: ```text common/ ├── platform.pri ├── compiler.pri └── version.pri ``` 用于统一处理: * 编译器配置 * 平台判断 * Debug / Release * 版本号 * 编译宏 * 输出目录 * 平台相关配置 例如: ```qmake win32 { DEFINES += QM_PLATFORM_WINDOWS } unix { DEFINES += QM_PLATFORM_LINUX } ``` --- # App 与 Module 的关系 App 不应该复制模块源码。 例如: ```text monitor simulator configurator ``` 都需要日志功能。 不应该: ```text monitor/ log.cpp simulator/ log.cpp configurator/ log.cpp ``` 而应该: ```text modules/log/ log.cpp ``` 然后各个 App 通过: ```qmake include(../../modules/log/log.pri) ``` 使用。 --- # `.pri` 模块设计 每一个源码模块都应该拥有自己的 `.pri` 文件。 例如: ```text modules/property/ ├── property.h ├── property.cpp └── property.pri ``` `property.pri` 负责描述这个模块: * 源文件 * 头文件 * include 路径 * 编译宏 * Qt 模块 * 依赖模块 例如: ```qmake isEmpty(QM_PROPERTY_PRI_INCLUDED) { QM_PROPERTY_PRI_INCLUDED = 1 include(../config/config.pri) INCLUDEPATH += $$PWD HEADERS += \ $$PWD/property.h SOURCES += \ $$PWD/property.cpp } ``` 这样: ```text property.pri ``` 就是 Property 模块自己的“构建说明”。 --- # 模块依赖关系 模块之间允许存在依赖。 例如: ```text core ↑ config ↑ property ``` 表示: ```text property → config → core ``` 又例如: ```text core ↗ ↖ serial websocket ``` 表示: ```text serial → core websocket → core ``` 一个完整 App 可能是: ```text App ├── property │ └── config │ └── core │ ├── serial │ └── core │ └── websocket └── core ``` 这实际上是一张 **模块依赖 DAG(有向无环图)**。 因此,模块并不是简单的树结构。 --- # `.pri` 重复包含问题 考虑以下情况: ```text App ├── A.pri │ └── C.pri │ └── B.pri └── C.pri ``` 如果 App 同时: ```qmake include(A.pri) include(B.pri) ``` 那么: ```text C.pri ``` 理论上可能被处理两次。 因此,每个 `.pri` 都应该具有自己的 Include Guard。 例如: ```qmake isEmpty(QM_CORE_PRI_INCLUDED) { QM_CORE_PRI_INCLUDED = 1 INCLUDEPATH += $$PWD HEADERS += \ $$PWD/core.h SOURCES += \ $$PWD/core.cpp } ``` 这样无论: ```text A → C B → C ``` 还是: ```text A → B → C ``` 都不会重复添加 C 模块。 --- # `.pri` Include Guard 推荐统一格式: ```text QM__PRI_INCLUDED ``` 例如: ```qmake QM_CORE_PRI_INCLUDED QM_LOG_PRI_INCLUDED QM_CONFIG_PRI_INCLUDED QM_PROPERTY_PRI_INCLUDED QM_SERIAL_PRI_INCLUDED QM_WEBSOCKET_PRI_INCLUDED ``` 完整示例: ```qmake isEmpty(QM_CORE_PRI_INCLUDED) { QM_CORE_PRI_INCLUDED = 1 INCLUDEPATH += $$PWD HEADERS += \ $$PWD/core.h SOURCES += \ $$PWD/core.cpp } ``` --- # C++ Header Guard 与 `.pri` Guard 的区别 二者解决的是完全不同的问题。 C++: ```cpp #pragma once ``` 解决: > C++ 头文件被重复包含。 而 qmake: ```qmake isEmpty(QM_CORE_PRI_INCLUDED) { QM_CORE_PRI_INCLUDED = 1 ... } ``` 解决: > `.pri` 文件被 qmake 重复 `include()`。 因此两者都需要。 --- # App 的 `.pro` 例如: ```text apps/monitor/monitor.pro ``` 可以写成: ```qmake TEMPLATE = app QT += core gui widgets SOURCES += \ main.cpp include(../../modules/core/core.pri) include(../../modules/log/log.pri) include(../../modules/config/config.pri) include(../../modules/property/property.pri) include(../../modules/serial/serial.pri) include(../../modules/websocket/websocket.pri) ``` App 只需要声明自己直接使用的模块。 例如: ```text monitor ``` 直接使用: ```text property serial websocket ``` 那么: ```qmake include(../../modules/property/property.pri) include(../../modules/serial/serial.pri) include(../../modules/websocket/websocket.pri) ``` 即可。 不需要手动再次: ```qmake include(../../modules/config/config.pri) include(../../modules/core/core.pri) ``` 因为模块自身应该负责自己的直接依赖。 --- # 依赖声明原则 推荐遵循: > **谁依赖谁,谁负责声明。** 例如: ```text property → config config → core ``` 那么: ### property.pri ```qmake include(../config/config.pri) ``` ### config.pri ```qmake include(../core/core.pri) ``` App: ```qmake include(../../modules/property/property.pri) ``` 最终形成: ```text App ↓ property ↓ config ↓ core ``` 而不是让 App 自己维护完整依赖链。 --- # 不推荐的方式 不推荐: ```qmake # app.pro include(core.pri) include(config.pri) include(property.pri) include(serial.pri) include(websocket.pri) include(tcp.pri) ... ``` 然后每增加一个模块,都需要 App 手动维护所有底层依赖。 这样会导致 App 的 `.pro` 文件越来越臃肿。 正确方式是: ```text App ↓ 直接依赖 ↓ 模块自己管理依赖 ``` --- # 模块依赖方向 推荐保持清晰的层级关系: ```text App │ ┌──────┴──────┐ ↓ ↓ Module Widget │ ↓ Base Module │ ↓ Core ``` 例如: ```text core ↑ ├── log ├── config ├── serial ├── tcp └── websocket ↑ property ↑ App ``` 具体项目可以根据实际情况调整。 --- # 禁止循环依赖 禁止: ```text A → B B → A ``` 也禁止: ```text A → B → C → A ``` 例如: ```text property ↓ config ↓ property ``` 这种设计应该重构。 通常可以将公共部分下沉: ```text property ↓ core ↑ config ``` 如果两个模块共享某些基础能力,应当考虑把公共部分提取到更底层的模块。 --- # 为什么当前阶段不直接编译公共库 本项目当前选择: ```text 源码模块 ↓ .pri ↓ App ↓ 最终可执行文件 ``` 而不是: ```text 源码模块 ↓ LIB / DLL / SO ↓ App ``` 主要原因是: ### 1. 简化构建 不需要维护: ```text .lib .dll .a .so ``` 以及不同编译器、不同 Qt 版本、不同平台下的二进制兼容问题。 ### 2. 简化部署 最终 App 可以直接携带自身需要的代码。 不需要额外处理: ```text xxx.dll xxx.so ``` 的部署关系。 ### 3. 更适合当前工程阶段 对于一个正在快速迭代的上位机框架: ```text 模块代码 ↓ 直接进入 App ``` 开发调试非常直接。 ### 4. 更容易进行跨平台适配 对于未来需要支持: ```text Windows Linux 国产操作系统 国产 CPU 国产数据库 ``` 源码方式可以降低一部分二进制兼容问题。 --- # 未来是否可以改成库? 可以。 当前: ```text module ↓ .pri ↓ App ``` 未来可以演进为: ```text module ↓ static library ↓ App ``` 或者: ```text module ↓ shared library ↓ App ``` 模块自身的 C++ 接口可以保持不变。 因此: > 当前采用源码模块化,并不意味着以后不能转为库。 --- # `*_EXPORT` 宏 当前模块直接以源码形式编译到 App 中,因此通常不需要: ```cpp MYLIB_EXPORT ``` 例如: ```cpp class MYLIB_EXPORT MyClass { }; ``` 这种宏主要用于: ```text DLL / SO ``` 等二进制边界。 如果未来: ```text modules/log ``` 变成: ```text libqm_log.dll libqm_log.so ``` 再引入: ```cpp Q_DECL_EXPORT Q_DECL_IMPORT ``` 等机制即可。 --- # 顶层 qmake 工程 根目录: ```text qm/qm.pro ``` 可以作为整个工程的入口。 例如: ```qmake TEMPLATE = subdirs monitor.subdir = apps/monitor simulator.subdir = apps/simulator configurator.subdir = apps/configurator SUBDIRS += \ monitor \ simulator \ configurator ``` 这里的 `subdirs` 只负责: > 管理多个最终 App。 公共源码模块本身不是独立 qmake 工程,因此不需要放入: ```qmake SUBDIRS ``` 例如不应该: ```qmake SUBDIRS += core SUBDIRS += config SUBDIRS += property ``` 除非这些模块未来变成独立的 library project。 --- # 构建关系 整体构建关系: ```text qm.pro │ TEMPLATE = subdirs │ ┌──────────────┼──────────────┐ ↓ ↓ ↓ monitor simulator configurator │ │ │ ↓ ↓ ↓ *.pri *.pri *.pri │ │ │ ↓ ↓ ↓ modules modules modules ``` 最终: ```text qm ├── monitor ├── simulator └── configurator ``` 都是独立的最终程序。 --- # 一个完整的依赖示例 假设: ```text monitor ``` 需要: ```text Property Serial WebSocket ``` 那么: ```text monitor.pro ``` 只声明: ```qmake include(../../modules/property/property.pri) include(../../modules/serial/serial.pri) include(../../modules/websocket/websocket.pri) ``` 其中: ```text property ↓ config ↓ core ``` ```text serial ↓ core ``` ```text websocket ↓ core ``` 最终 qmake 自动得到: ```text monitor ├── property ├── config ├── serial ├── websocket └── core ``` 虽然: ```text core ``` 被多个模块依赖,但由于: ```qmake QM_CORE_PRI_INCLUDED ``` 的存在,只会被加入一次。 --- # 模块开发规范 每一个模块至少包含: ```text module/ ├── xxx.h ├── xxx.cpp └── xxx.pri ``` 复杂模块可以进一步: ```text module/ ├── include/ ├── src/ ├── private/ ├── resource/ ├── xxx.pri └── README.md ``` 模块 `.pri` 负责: ```text 1. 模块自身源码 2. 模块自身头文件 3. INCLUDEPATH 4. Qt 依赖 5. 编译宏 6. 直接依赖模块 ``` App `.pro` 不负责维护模块内部依赖。 --- # 模块的独立性 模块应该尽量做到: ```text 低耦合 高内聚 单向依赖 明确职责 ``` 例如: ```text serial ``` 不应该依赖: ```text property UI chart dock main window ``` 而应该只依赖它真正需要的基础模块: ```text serial ↓ core ``` --- # UI 与功能分离 对于一个功能模块,如果同时存在: ```text 核心功能 UI ``` 建议分开。 例如 Property: ```text modules/property/ property.h property.cpp property.pri widgets/property/ property_editor.h property_editor.cpp property.pri ``` 这样: ```text 纯业务代码 ``` 可以在无 UI 环境下使用,而: ```text Qt Widgets ``` 只是其可选表现层。 --- # Qt 模块依赖 模块自己的 Qt 依赖也应该尽量在模块中声明。 例如: ```qmake QT += core ``` 需要 Widgets: ```qmake QT += core gui widgets ``` 例如: ```text widgets/property ``` 可以: ```qmake QT += core gui widgets ``` 而: ```text modules/core ``` 不应该因为某个 UI 模块需要 Widgets 就被迫: ```qmake QT += widgets ``` 保持底层模块轻量化。 --- # 命名规范 项目整体建议采用统一的 `qm` 前缀体系。 例如: ```text QM_CORE_PRI_INCLUDED QM_LOG_PRI_INCLUDED QM_CONFIG_PRI_INCLUDED QM_PROPERTY_PRI_INCLUDED ``` C++ 命名空间保持简短。 推荐: ```cpp namespace qm { } ``` 如果进一步拆分,可以采用简短的小写命名空间。 原则: * 命名空间使用小写 * 命名空间尽量不超过 3 个字符 * 模块名保持语义明确 * 避免过长的层级命名 * 公共 API 命名保持稳定 --- # Git 仓库原则 推荐将源码工程本身完整纳入 Git。 例如: ```text qm/ ├── apps/ ├── modules/ ├── widgets/ ├── thirdparty/ ├── common/ ├── qm.pro └── README.md ``` 通常不提交: ```text build/ *.obj *.o *.exe *.dll *.lib *.a *.so *.user ``` 具体忽略规则根据开发环境补充。 --- # 推荐的长期架构 随着项目发展,可以逐渐形成: ```text ┌──────────────┐ │ Apps │ └──────┬───────┘ │ ┌─────────────────┼─────────────────┐ ↓ ↓ ↓ Business Widgets Plugin │ │ │ └────────────┬────┴─────────────────┘ ↓ Modules │ ┌──────────────┼──────────────┐ ↓ ↓ ↓ Config Log Communication │ │ │ └──────────────┼──────────────┘ ↓ Core ``` 最终形成: ```text App Layer ↓ Business / UI Layer ↓ Functional Module Layer ↓ Core Layer ↓ Qt / OS ``` --- # 未来模块规划 根据上位机常见需求,后续可以逐步增加: ```text modules/ ├── core/ # 核心基础设施 ├── log/ # 日志 ├── config/ # 配置管理 ├── property/ # Property 系统 ├── serial/ # 串口 ├── tcp/ # TCP ├── udp/ # UDP ├── websocket/ # WebSocket ├── database/ # 数据库 ├── protocol/ # 通信协议 ├── timer/ # 定时任务 ├── thread/ # 线程 ├── crash/ # 崩溃处理 ├── plugin/ # 插件系统 ├── ipc/ # 进程间通信 └── resource/ # 资源管理 ``` UI: ```text widgets/ ├── property/ ├── log/ ├── chart/ ├── dock/ ├── table/ ├── tree/ ├── terminal/ ├── monitor/ └── common/ ``` --- # 最重要的几条原则 整个工程最终遵循以下原则: ### 1. App 是产品 ```text apps/ ``` 存放最终交付的软件。 ### 2. Module 是能力 ```text modules/ ``` 存放可复用的功能模块。 ### 3. Widget 是界面 ```text widgets/ ``` 存放可复用的 Qt UI 组件。 ### 4. `.pri` 是模块描述文件 `.pri` 不等于库。 它只是告诉 qmake: ```text 这个模块有哪些源码 这个模块需要什么 这个模块依赖谁 ``` ### 5. 模块自己声明直接依赖 ```text A → B ``` 由 A 的 `.pri` 声明 B。 ### 6. `.pri` 必须可重复 Include 使用: ```qmake isEmpty(QM_xxx_PRI_INCLUDED) { QM_xxx_PRI_INCLUDED = 1 ... } ``` 避免重复加入。 ### 7. 依赖必须单向 形成: ```text DAG ``` 而不是: ```text 循环依赖 ``` ### 8. 当前不引入公共二进制库 源码直接进入 App: ```text .pri → App ``` 以后有需要再演进为: ```text .pri → static/shared library → App ``` ### 9. App 只关注自己使用什么 App 不应该关心: ```text property 依赖 config config 依赖 core ``` 这些应该由模块自己解决。 --- # 总结 本项目采用一种简单但可持续扩展的 Qt 工程架构: ```text Git Repository │ ┌─────────────┴─────────────┐ │ │ Apps Common │ │ ↓ ↓ *.pro *.pri │ ↓ Module / Widget │ ↓ *.pri │ ↓ Module Dependencies │ ↓ Core │ ↓ Qt / Operating System ``` 核心思想可以概括为: > **App 负责产品,Module 负责能力,Widget 负责界面,`.pri` 负责模块组织,qmake 负责构建。** 当前阶段所有公共模块以源码形式复用,不强制引入二进制库。通过 `.pri` 的 Include Guard 和单向依赖关系,使整个工程能够在保持简单构建方式的同时,逐渐成长为一个完整的 Qt 上位机基础框架。 随着项目成熟,可以在不破坏模块接口的情况下,进一步引入: ```text Static Library Shared Library Plugin 独立进程 模块热加载 ``` 从而逐步形成一套完整的、可复用的 Qt 上位机开发基础设施。