Qt C++ 实战:手把手教你开发系统资源监控工具

📅 2026/7/21 8:03:59
Qt C++ 实战:手把手教你开发系统资源监控工具
最近在带学生做课程设计发现很多同学对系统资源监控工具的实现很感兴趣但网上的资料要么过于零散要么只讲理论没有完整代码。本文将手把手带你用 Qt C 开发一个功能完整的系统资源监控工具从环境搭建到界面设计再到核心监控逻辑的实现最后打包成可执行文件。这个项目非常适合作为 C/Qt 的课程设计或期末大作业代码结构清晰功能模块化你可以直接复用或在此基础上进行扩展。1. 项目概述与核心功能系统资源监控工具顾名思义就是用来实时监控计算机硬件资源使用情况的软件比如 CPU 使用率、内存占用、磁盘空间、网络流量等。对于学习 C 和 Qt 的同学来说这是一个绝佳的实战项目因为它综合运用了面向对象编程、多线程、GUI 设计、系统 API 调用等多个核心知识点。我们本次要实现的监控工具将包含以下核心功能模块CPU 监控实时显示 CPU 的总使用率以及每个逻辑核心的使用率。内存监控显示物理内存和虚拟内存的总量、已用量、使用率。磁盘监控列出所有磁盘分区并显示其总空间、可用空间和使用率。网络监控显示当前系统的网络连接状态、上传/下载速度基础版可先显示网络接口信息。进程管理显示当前运行的所有进程列表包括 PID、进程名、CPU/内存占用并提供结束进程的功能需管理员权限。图形化界面使用 Qt 的QChart或自定义绘图以折线图、进度条、表格等形式直观展示数据。项目最终会呈现一个主界面通过标签页QTabWidget或仪表盘的形式组织这些监控信息界面友好数据刷新实时。2. 开发环境与工具准备在开始编码之前我们需要准备好开发环境。一个稳定、配置正确的环境是项目成功的第一步。2.1 操作系统本项目主要面向 Windows 平台进行开发因为其系统 API 较为统一。部分功能如获取进程列表在 Linux/macOS 上实现方式不同但核心的 Qt 界面逻辑是跨平台的。本文示例代码将以 Windows 为例。2.2 Qt 安装与配置Qt 是一个强大的跨平台 C 图形用户界面应用程序框架。我们需要安装 Qt 的开发环境。下载 Qt访问 Qt 官网的下载页面。对于学习和个人使用可以选择开源版本。建议下载Qt Online Installer它允许你自定义安装组件。安装组件运行安装程序后在组件选择页面务必勾选以下内容最新稳定版本的 Qt例如Qt 6.5.x或Qt 5.15.x。建议新手选择Qt 6.5.x它更现代。对应版本的 MinGW 编译器例如MinGW 11.2.0 64-bit。这是 Windows 上编译 Qt 程序常用的编译器。如果你安装了 Visual Studio也可以选择对应的MSVC套件。Qt Creator这是 Qt 官方的集成开发环境IDE集成了代码编辑、UI 设计、编译调试等功能非常适合初学者。验证安装安装完成后打开 Qt Creator。创建一个新的Qt Widgets Application项目编译并运行默认的窗口程序。如果能成功弹出窗口说明环境配置成功。2.3 解决常见环境问题在安装或运行 Qt 项目时你可能会遇到以下问题error: Microsoft Visual C 14.0 or greater is required这个问题通常在使用pip安装某些 Python 包时出现但如果你在编译某些 Qt 项目或第三方 C 库时遇到也需要安装 Visual C 构建工具。解决方案访问微软官网下载并安装Visual Studio Build Tools或Visual Studio Installer在安装时选择“使用 C 的桌面开发”工作负载它会包含所需的 MSVC 编译器和库。qt.qpa.plugin: Could not load the Qt platform plugin xcb这是在 Linux 环境下运行 Qt 程序时常见的错误原因是缺少 GUI 相关的依赖库。解决方案在 Ubuntu/Debian 系统上可以尝试安装以下包sudo apt-get install libxcb-xinerama0。更全面的方法是安装sudo apt-get install libxcb*系列库。如果问题依旧请检查 Qt 安装是否完整以及程序是否链接了正确的 Qt 库路径。3. 项目架构与核心类设计在动手写代码前好的设计能让后续开发事半功倍。我们将采用Model-View的思想来设计这个监控工具。Model模型负责数据的获取和计算。我们将创建几个独立的“监控器”类它们运行在后台线程中定期采集系统数据如 CPU 使用率。View视图负责数据的展示。即我们的 Qt 窗口界面包含各种图表、进度条和表格。Controller控制器在 Qt 中通常利用信号Signal和槽Slot机制来充当控制器角色。当 Model 获取到新数据时发出信号View 中的部件连接到这些信号更新显示。核心类设计SystemMonitor(主控制器类)继承自QMainWindow。它是应用程序的主窗口负责创建和管理各个监控视图标签页并启动后台数据采集线程。CpuMonitor/MemoryMonitor/DiskMonitor/NetworkMonitor/ProcessMonitor(数据模型类)这些类继承自QObject以便使用信号槽。它们包含采集特定资源数据的逻辑并定期发出带有新数据的信号。例如CpuMonitor类会有一个updateCpuUsage()方法该方法调用系统 API 获取 CPU 时间并计算使用率。ChartWidget/DashboardWidget(自定义视图部件)继承自QWidget或QChartView。这些是自定义的 Qt 部件用于绘制折线图、仪表盘等它们会连接到对应 Monitor 的信号实时更新图形。数据流 后台监控线程定时触发 → 调用Monitor类的数据采集方法 →Monitor类计算得到新数据 → 发出dataUpdated信号 → 前台的ChartWidget接收到信号 → 调用updateData槽函数刷新界面。4. 核心功能模块实现详解接下来我们深入每个功能模块看看如何用 C 和 Qt 实现。4.1 CPU 监控模块实现CPU 使用率的计算原理是在两个很短的时间间隔内分别获取 CPU 总的空闲时间和总的工作时间通过计算差值来得出这段时间内的使用率。Windows 平台实现思路Windows 提供了Pdh(Performance Data Helper) API 来方便地获取性能计数器数据这是最推荐的方式。首先在项目文件 (.pro) 中添加链接库QT core gui charts CONFIG c17 LIBS -lpdh然后创建CpuMonitor类// cpumonitor.h #ifndef CPUMONITOR_H #define CPUMONITOR_H #include QObject #include QTimer #include windows.h #include pdh.h #pragma comment(lib, pdh.lib) class CpuMonitor : public QObject { Q_OBJECT public: explicit CpuMonitor(QObject *parent nullptr); ~CpuMonitor(); double getTotalCpuUsage() const { return m_totalCpuUsage; } QVectordouble getCoreCpuUsage() const { return m_coreUsages; } public slots: void startMonitoring(int intervalMs 1000); // 开始监控指定刷新间隔 void stopMonitoring(); // 停止监控 signals: void cpuDataUpdated(double totalUsage, const QVectordouble coreUsages); private slots: void updateCpuData(); private: QTimer *m_timer; double m_totalCpuUsage; QVectordouble m_coreUsages; // PDH 相关句柄 PDH_HQUERY m_cpuQuery; PDH_HCOUNTER m_totalCpuCounter; QVectorPDH_HCOUNTER m_coreCpuCounters; void initPdhCounters(); void cleanupPdhCounters(); }; #endif // CPUMONITOR_H// cpumonitor.cpp #include cpumonitor.h #include QDebug CpuMonitor::CpuMonitor(QObject *parent) : QObject(parent), m_timer(new QTimer(this)), m_totalCpuUsage(0.0) { connect(m_timer, QTimer::timeout, this, CpuMonitor::updateCpuData); initPdhCounters(); } CpuMonitor::~CpuMonitor() { stopMonitoring(); cleanupPdhCounters(); } void CpuMonitor::initPdhCounters() { PdhOpenQuery(nullptr, 0, m_cpuQuery); // 添加总 CPU 使用率计数器 PdhAddEnglishCounter(m_cpuQuery, L\\Processor(_Total)\\% Processor Time, 0, m_totalCpuCounter); // 获取逻辑处理器数量并添加每个核心的计数器 SYSTEM_INFO sysInfo; GetSystemInfo(sysInfo); int numCores sysInfo.dwNumberOfProcessors; m_coreUsages.resize(numCores); m_coreCpuCounters.resize(numCores); for (int i 0; i numCores; i) { QString counterPath QString(\\Processor(%1)\\% Processor Time).arg(i); PdhAddEnglishCounter(m_cpuQuery, counterPath.toStdWString().c_str(), 0, m_coreCpuCounters[i]); } // 首次收集数据初始化计数器 PdhCollectQueryData(m_cpuQuery); } void CpuMonitor::cleanupPdhCounters() { if (m_cpuQuery) { PdhCloseQuery(m_cpuQuery); m_cpuQuery nullptr; } } void CpuMonitor::startMonitoring(int intervalMs) { if (!m_timer-isActive()) { m_timer-start(intervalMs); } } void CpuMonitor::stopMonitoring() { if (m_timer-isActive()) { m_timer-stop(); } } void CpuMonitor::updateCpuData() { PDH_FMT_COUNTERVALUE counterVal; // 收集当前查询数据 PdhCollectQueryData(m_cpuQuery); // 获取总 CPU 使用率 PdhGetFormattedCounterValue(m_totalCpuCounter, PDH_FMT_DOUBLE, nullptr, counterVal); m_totalCpuUsage counterVal.doubleValue; // 获取每个核心的 CPU 使用率 for (int i 0; i m_coreCpuCounters.size(); i) { PdhGetFormattedCounterValue(m_coreCpuCounters[i], PDH_FMT_DOUBLE, nullptr, counterVal); m_coreUsages[i] counterVal.doubleValue; } // 发出数据更新信号 emit cpuDataUpdated(m_totalCpuUsage, m_coreUsages); }界面展示 在 Qt 主窗口中我们可以创建一个标签页使用QProgressBar显示总使用率并用一个QGridLayout排列多个进度条来显示每个核心的使用率。同时可以使用QChart来绘制历史使用率的折线图。4.2 内存监控模块实现内存信息可以通过 Windows 的GlobalMemoryStatusEx函数获取。// memorymonitor.h #ifndef MEMORYMONITOR_H #define MEMORYMONITOR_H #include QObject #include QTimer struct MemoryInfo { quint64 totalPhys; // 物理内存总量 (字节) quint64 availPhys; // 可用物理内存 (字节) quint64 totalPageFile; // 页面文件总量 (字节) quint64 availPageFile; // 可用页面文件 (字节) quint64 totalVirtual; // 虚拟内存总量 (字节) quint64 availVirtual; // 可用虚拟内存 (字节) double physUsage; // 物理内存使用率 (%) double virtualUsage; // 虚拟内存使用率 (%) }; class MemoryMonitor : public QObject { Q_OBJECT public: explicit MemoryMonitor(QObject *parent nullptr); MemoryInfo getMemoryInfo() const { return m_memInfo; } public slots: void startMonitoring(int intervalMs 2000); void stopMonitoring(); signals: void memoryDataUpdated(const MemoryInfo info); private slots: void updateMemoryData(); private: QTimer *m_timer; MemoryInfo m_memInfo; }; #endif // MEMORYMONITOR_H// memorymonitor.cpp #include memorymonitor.h #include windows.h #include QDebug MemoryMonitor::MemoryMonitor(QObject *parent) : QObject(parent), m_timer(new QTimer(this)) { connect(m_timer, QTimer::timeout, this, MemoryMonitor::updateMemoryData); } void MemoryMonitor::updateMemoryData() { MEMORYSTATUSEX memStatus; memStatus.dwLength sizeof(memStatus); if (GlobalMemoryStatusEx(memStatus)) { m_memInfo.totalPhys memStatus.ullTotalPhys; m_memInfo.availPhys memStatus.ullAvailPhys; m_memInfo.totalPageFile memStatus.ullTotalPageFile; m_memInfo.availPageFile memStatus.ullAvailPageFile; m_memInfo.totalVirtual memStatus.ullTotalVirtual; m_memInfo.availVirtual memStatus.ullAvailVirtual; // 计算使用率 m_memInfo.physUsage (1.0 - (double)m_memInfo.availPhys / m_memInfo.totalPhys) * 100.0; m_memInfo.virtualUsage (1.0 - (double)m_memInfo.availPageFile / m_memInfo.totalPageFile) * 100.0; emit memoryDataUpdated(m_memInfo); } else { qDebug() Failed to get memory status.; } } void MemoryMonitor::startMonitoring(int intervalMs) { if (!m_timer-isActive()) { updateMemoryData(); // 立即更新一次 m_timer-start(intervalMs); } } void MemoryMonitor::stopMonitoring() { if (m_timer-isActive()) { m_timer-stop(); } }界面展示使用QLabel显示具体数值如 “8.0/16.0 GB (50%)”并用QProgressBar直观展示使用率进度条。4.3 进程管理模块实现获取进程列表需要使用ToolHelp32API。同时为了不阻塞 GUI 线程获取进程信息的操作应在单独的线程中进行。// processmonitor.h (简化版主要展示获取进程列表) #ifndef PROCESSMONITOR_H #define PROCESSMONITOR_H #include QObject #include QTimer #include QVector #include windows.h #include tlhelp32.h struct ProcessInfo { DWORD pid; QString name; QString exePath; double cpuUsage; // 需要复杂计算此处简化 quint64 memoryUsage; // 工作集内存 }; class ProcessMonitor : public QObject { Q_OBJECT public: explicit ProcessMonitor(QObject *parent nullptr); QVectorProcessInfo getProcessList() const; public slots: void refreshProcessList(); signals: void processListUpdated(const QVectorProcessInfo list); private: QVectorProcessInfo m_processList; }; #endif // PROCESSMONITOR_H// processmonitor.cpp #include processmonitor.h #include QDebug #include psapi.h // 用于获取更多进程信息 #pragma comment(lib, psapi.lib) ProcessMonitor::ProcessMonitor(QObject *parent) : QObject(parent) { } QVectorProcessInfo ProcessMonitor::getProcessList() const { return m_processList; } void ProcessMonitor::refreshProcessList() { QVectorProcessInfo newList; HANDLE hSnapshot CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot INVALID_HANDLE_VALUE) { qDebug() CreateToolhelp32Snapshot failed.; return; } PROCESSENTRY32 pe32; pe32.dwSize sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, pe32)) { do { ProcessInfo info; info.pid pe32.th32ProcessID; info.name QString::fromWCharArray(pe32.szExeFile); // 获取进程句柄以查询内存信息需要 PROCESS_QUERY_INFORMATION 和 PROCESS_VM_READ 权限 HANDLE hProcess OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, info.pid); if (hProcess) { PROCESS_MEMORY_COUNTERS pmc; if (GetProcessMemoryInfo(hProcess, pmc, sizeof(pmc))) { info.memoryUsage pmc.WorkingSetSize; // 工作集大小 } CloseHandle(hProcess); } // CPU 使用率计算较为复杂需要记录上一次的进程时间此处暂不实现 info.cpuUsage 0.0; newList.append(info); } while (Process32Next(hSnapshot, pe32)); } CloseHandle(hSnapshot); m_processList newList; emit processListUpdated(m_processList); }界面展示使用QTableWidget来展示进程列表。表头可以设置为“PID”、“进程名”、“CPU%”、“内存”。将ProcessMonitor发出的processListUpdated信号连接到表格的更新槽函数。还可以在表格最后一列添加一个“结束任务”按钮其点击事件调用TerminateProcessAPI注意这需要管理员权限且强制结束进程可能导致数据丢失应谨慎使用并给出提示。5. 图形界面整合与数据可视化有了数据模型我们需要一个漂亮的界面来展示它们。Qt 的QChart模块非常适合做数据可视化。5.1 使用 QChart 绘制 CPU 历史曲线首先在 .pro 文件中我们已经添加了charts模块。// cpuchartwidget.h #ifndef CPUCHARTWIDGET_H #define CPUCHARTWIDGET_H #include QWidget #include QtCharts QT_CHARTS_USE_NAMESPACE class CpuChartWidget : public QChartView { Q_OBJECT public: explicit CpuChartWidget(QWidget *parent nullptr); public slots: void updateCpuData(double totalUsage, const QVectordouble coreUsages); private: QChart *m_chart; QLineSeries *m_totalSeries; QVectorQLineSeries* m_coreSeries; QValueAxis *m_axisX; QValueAxis *m_axisY; int m_dataCount; const int MAX_DATA_POINTS 60; // 显示最近60个数据点 }; #endif // CPUCHARTWIDGET_H// cpuchartwidget.cpp #include cpuchartwidget.h CpuChartWidget::CpuChartWidget(QWidget *parent) : QChartView(parent), m_dataCount(0) { m_chart new QChart(); m_chart-setTitle(CPU Usage History); m_chart-setAnimationOptions(QChart::NoAnimation); this-setChart(m_chart); this-setRenderHint(QPainter::Antialiasing); // 创建坐标轴 m_axisX new QValueAxis; m_axisX-setRange(0, MAX_DATA_POINTS); m_axisX-setLabelFormat(%d); m_axisX-setTitleText(Time (s)); m_axisY new QValueAxis; m_axisY-setRange(0, 100); m_axisY-setTitleText(Usage (%)); m_chart-addAxis(m_axisX, Qt::AlignBottom); m_chart-addAxis(m_axisY, Qt::AlignLeft); // 创建总 CPU 使用率曲线 m_totalSeries new QLineSeries(); m_totalSeries-setName(Total CPU); m_chart-addSeries(m_totalSeries); m_totalSeries-attachAxis(m_axisX); m_totalSeries-attachAxis(m_axisY); } void CpuChartWidget::updateCpuData(double totalUsage, const QVectordouble coreUsages) { // 更新总 CPU 曲线 m_totalSeries-append(m_dataCount, totalUsage); // 限制数据点数量保持图表清晰 if (m_totalSeries-count() MAX_DATA_POINTS) { m_totalSeries-remove(0); } // 动态创建或更新每个核心的曲线示例只处理第一个核心 // 实际项目中需要根据 coreUsages 的数量动态管理 QLineSeries if (coreUsages.size() 0) { // 假设我们只显示第一个核心的曲线作为示例 if (m_coreSeries.isEmpty()) { QLineSeries *coreSeries new QLineSeries(); coreSeries-setName(Core 0); coreSeries-setColor(Qt::red); m_chart-addSeries(coreSeries); coreSeries-attachAxis(m_axisX); coreSeries-attachAxis(m_axisY); m_coreSeries.append(coreSeries); } m_coreSeries[0]-append(m_dataCount, coreUsages[0]); if (m_coreSeries[0]-count() MAX_DATA_POINTS) { m_coreSeries[0]-remove(0); } } m_dataCount; m_axisX-setRange(m_dataCount - MAX_DATA_POINTS, m_dataCount); }在主窗口中我们将CpuMonitor的cpuDataUpdated信号连接到CpuChartWidget的updateCpuData槽即可。5.2 主窗口界面布局使用 Qt Designer 设计主界面非常高效。我们可以创建一个QMainWindow中心部件使用QTabWidget每个标签页放置不同的监控部件。CPU 标签页放置一个CpuChartWidget和一组QProgressBar显示核心详情。内存标签页放置几个QLabel和QProgressBar显示物理/虚拟内存。进程标签页放置一个QTableWidget和一个QPushButton刷新按钮。磁盘/网络标签页类似布局。关键代码连接信号与槽// 在 MainWindow 的构造函数中 m_cpuMonitor new CpuMonitor(this); m_cpuChartWidget new CpuChartWidget(ui-cpuTab); // ui-cpuTab 是放置图表的 Widget connect(m_cpuMonitor, CpuMonitor::cpuDataUpdated, m_cpuChartWidget, CpuChartWidget::updateCpuData); connect(m_cpuMonitor, CpuMonitor::cpuDataUpdated, this, MainWindow::updateCpuProgressBars); // 更新进度条的槽函数 m_cpuMonitor-startMonitoring(1000); // 每秒更新一次6. 项目构建、打包与部署6.1 构建项目在 Qt Creator 中选择正确的构建套件Kit然后点击“构建”即可。确保所有.h和.cpp文件都已正确添加到项目.pro文件中。6.2 解决运行时依赖发布Qt 程序编译后不能直接将.exe文件复制到其他没有 Qt 环境的电脑上运行会提示缺少 DLL。我们需要将程序依赖的 Qt 库一起打包。使用windeployqt工具推荐windeployqt是 Qt 自带的部署工具能自动扫描.exe文件并将其依赖的 Qt DLL 复制到同一目录下。在 Qt 安装目录下找到windeployqt.exe通常在Qt\版本\编译器\bin目录下。打开命令行切换到你的程序编译输出目录包含.exe文件的release或debug文件夹。执行命令windeployqt your_app_name.exe工具会自动复制所有必需的 Qt DLL 和插件如图形后端qwindows.dll位于plugins/platforms目录。你可能还需要手动复制编译器运行时库如libgcc_s_*.dll,libstdc-*.dll,libwinpthread-*.dll对于 MinGW。6.3 创建安装包为了更专业地分发可以使用Inno Setup或NSIS等工具将程序文件、依赖库和必要的资源文件打包成一个标准的 Windows 安装程序.exe或.msi。安装包可以创建开始菜单快捷方式、设置文件关联等。7. 常见问题与调试技巧在开发过程中你可能会遇到以下问题程序运行无界面或瞬间退出原因可能是事件循环没有启动或者程序在构造函数中发生崩溃。排查在main函数和构造函数开头添加qDebug()输出看程序执行到哪一步。检查是否在非 GUI 线程中操作了界面部件。获取系统信息返回错误或零值原因API 调用失败可能是权限不足如获取某些进程信息或句柄未正确初始化/关闭。排查检查每个 Windows API 的返回值如OpenProcess返回NULL吗。使用GetLastError()获取错误代码并在网上搜索。确保遵循 API 调用规范如先CreateToolhelp32Snapshot再Process32First最后CloseHandle。界面卡顿特别是刷新进程列表时原因数据获取和界面更新都在主线程GUI线程中进行耗时的操作如遍历所有进程并获取详细信息会阻塞界面渲染。解决将耗时的数据采集工作放在单独的QThread中。Monitor类本身可以在一个线程中运行通过信号将结果发送给主线程更新界面。这就是我们设计时采用信号槽机制的原因。打包后的程序在其他电脑上运行提示缺少VCRUNTIME140.dll等原因缺少 Visual C 运行时库。解决如果你的程序是用 MSVC 编译的需要目标电脑安装对应版本的Visual C Redistributable。可以在安装包中附带或者提示用户自行从微软官网下载安装。MinGW 编译的程序则需要附带对应的 GCC 运行时 DLL。8. 功能扩展与优化建议一个基础的监控工具已经完成但你可以让它更强大这非常适合作为大作业的加分项历史数据记录与导出将监控数据如 CPU、内存历史定期保存到 SQLite 数据库或 CSV 文件中并提供一个界面来回看历史趋势或导出报告。阈值告警允许用户设置 CPU、内存的使用率阈值。当超过阈值时程序可以弹出系统通知使用QSystemTrayIcon、改变界面颜色或播放提示音。更多系统信息监控 GPU 使用率需要借助 NVAPI 或 AMD ADL SDK、主板温度、风扇转速需要硬件监控库如 LibreHardwareMonitor 或 OpenHardwareMonitor 的接口。网络流量监控实现更精确的实时网速监控可以使用GetIfTable等 API 获取网络接口的字节计数计算差值得到速度。跨平台支持使用#ifdef _WIN32等预编译指令为 Linux 和 macOS 编写相应的系统信息获取代码如 Linux 下读取/proc/stat,/proc/meminfo等文件。美化界面使用 Qt 样式表QSS为界面换肤制作更现代化的扁平化或拟物化设计。资源占用优化优化数据采集频率避免不必要的刷新对于进程列表这种数据量大的视图可以考虑分页或虚拟滚动。通过这个项目的实践你不仅能掌握 Qt C 图形界面开发的全流程还能深入理解 Windows 系统编程、多线程编程以及模块化软件设计的思想。