在实际开发中,无论是进行系统性能分析、资源瓶颈排查,还是构建一个简单的桌面监控小工具,掌握如何获取系统核心资源(如CPU、内存、磁盘)的使用情况都是一项基础且实用的技能。对于使用Qt C++进行桌面应用开发的同学来说,这不仅是课程设计或毕业大作业中一个很好的选题方向,也是深入理解操作系统底层API和Qt跨平台特性的绝佳实践。
本文将带你从零开始,使用Qt C++框架,构建一个跨平台的系统资源监控工具。我们将重点解决几个核心问题:如何在不同操作系统(Windows/Linux)上获取CPU使用率、内存占用和磁盘空间信息;如何将这些数据通过Qt的图表组件(如QChart)进行可视化;以及如何将整个项目打包成独立的可执行文件。整个过程会涵盖环境搭建、核心代码实现、界面设计、数据可视化以及项目打包,最终你将得到一个功能完整、界面直观的监控工具,可以直接用于课程设计或作为个人技术积累。
1. 理解系统资源监控的核心概念与跨平台挑战
在动手编码之前,我们需要明确要监控哪些资源,以及在不同操作系统上获取这些数据的差异。一个基本的系统监控工具通常关注以下核心指标:
- CPU使用率 :反映处理器在单位时间内的繁忙程度。计算方式通常是统计CPU在用户态、内核态以及空闲状态下的时间差。
- 内存使用情况 :包括物理内存总量、已使用量、空闲量,以及交换分区(Swap)的使用情况。
- 磁盘空间 :显示各个磁盘分区或挂载点的总容量、已用空间和可用空间。
这些信息在Windows和Linux系统上,需要通过不同的系统API或读取不同的系统文件来获取。这是开发跨平台监控工具的第一个挑战。Qt框架本身提供了一些跨平台的抽象(如
QStorageInfo
用于磁盘信息),但对于CPU和内存的详细数据,我们仍需编写平台相关的代码,并通过条件编译(
#ifdef
)来隔离。
第二个挑战是数据的动态更新与可视化。我们需要一个定时器(
QTimer
)来周期性地(例如每秒一次)采集数据,并实时更新到用户界面上。Qt的图表模块
QtCharts
非常适合用来绘制动态变化的折线图或柱状图,直观展示CPU和内存的使用趋势。
2. 环境准备与项目创建
2.1 开发环境搭建
首先,确保你的开发环境已就绪。你需要安装以下组件:
-
Qt SDK
:从Qt官网或国内镜像站下载Qt Online Installer或离线安装包。建议选择长期支持版本(如Qt 5.15.x 或 Qt 6.2+)。安装时,务必勾选与你编译器匹配的Qt套件(如
MSVC 2019 64-bit)以及Qt Charts模块。 -
C++编译器
:
- Windows :安装Visual Studio 2019或2022,并确保安装时包含了“使用C++的桌面开发”工作负载。这会自动安装所需的Microsoft Visual C++ Redistributable。如果遇到“microsoft visual c++ 14.0 or greater is required”错误,请单独下载并安装对应版本的VC++运行库。
-
Linux
:通常系统自带g++,使用包管理器安装即可(如
sudo apt install g++)。
- 集成开发环境(IDE) :推荐使用Qt Creator,它与Qt框架集成度最高。也可以使用VSCode配合CMake和Qt插件,但这需要额外的配置。
2.2 创建Qt Widgets项目
打开Qt Creator,按照以下步骤创建项目:
-
选择
文件->新建文件或项目。 -
选择
Application->Qt Widgets Application,点击选择。 -
输入项目名称(如
SystemMonitor)和存储路径。 -
在
Kit Selection页面,选择你安装的Qt套件(如Desktop Qt 5.15.2 MSVC2019 64bit)。 -
在
类信息页面,基类选择QMainWindow,类名可保持为MainWindow。这将自动生成一个带菜单栏、工具栏和状态栏的主窗口。 - 完成创建。
2.3 配置项目文件(.pro)
创建项目后,我们需要修改
.pro
文件来添加必要的模块。由于我们要使用图表,必须链接
QtCharts
模块。
打开项目根目录下的
.pro
文件(如
SystemMonitor.pro
),在末尾添加以下行:
# 添加charts模块
QT += charts
# 如果是Windows,可能需要链接额外的库(如Pdh.lib用于性能数据)
win32 {
LIBS += -lpdh
}
# 如果是Linux,可能需要链接额外的库(如读取/proc文件系统通常不需要额外链接)
unix:!macx {
# 对于Linux,通常不需要额外链接,但确保有基本库
LIBS += -lrt
}
保存
.pro
文件后,Qt Creator会提示你重新运行qmake来应用更改。点击
构建
->
运行qmake
。
3. 设计监控工具的用户界面
我们将使用Qt Designer来设计主界面。双击项目文件树中的
mainwindow.ui
文件,打开设计器。
我们的界面可以包含以下区域:
- 顶部 :标题和全局信息(如当前时间、系统运行时间)。
-
中部左侧
:使用
QChartView(需要先提升为QChartView控件)来显示CPU和内存使用率的实时折线图。 -
中部右侧
:使用多个
QLabel和QProgressBar来直观显示CPU核心、内存、交换空间的实时使用百分比和具体数值。 -
底部
:使用
QTableWidget或QTreeWidget来显示各个磁盘分区的详细信息(盘符、总大小、已用、可用、使用率)。 - 状态栏 :用于显示数据更新时间或临时信息。
设计步骤:
-
从左侧
Widget Box拖拽Vertical Layout、Horizontal Layout、Group Box、Label、Progress Bar、Table Widget等控件到中心区域。 - 合理使用布局管理器(Layouts)来确保窗口缩放时控件能自适应。
-
为需要动态更新的控件设置好
objectName,以便在代码中引用。例如:-
CPU进度条:
cpuProgressBar -
内存进度条:
memProgressBar -
CPU数值标签:
labelCpuUsage -
图表视图:
chartView(需要先放置一个QWidget,然后右键 ->提升为...,在“提升的类名称”中填入QChartView,头文件填入#include <QtCharts/QChartView>,点击添加并提升)。
-
CPU进度条:
一个简单的界面布局示例如下(通过布局管理器组合):
[标题标签]
---------------------------------
[图表区域 (QChartView)] | [CPU/内存信息面板 (GroupBox)]
| - CPU: [ProgressBar] [Label]
| - 内存: [ProgressBar] [Label]
| - 交换: [ProgressBar] [Label]
---------------------------------
[磁盘信息表格 (QTableWidget)]
---------------------------------
[状态栏]
4. 实现跨平台的系统资源获取逻辑
这是工具的核心。我们将创建单独的类来封装资源获取功能,以实现更好的代码组织。在项目中新建一个C++类,命名为
SystemInfo
。
4.1
systeminfo.h
头文件
#ifndef SYSTEMINFO_H
#define SYSTEMINFO_H
#include <QObject>
#include <QVector>
#include <QStorageInfo>
class SystemInfo : public QObject
{
Q_OBJECT
public:
explicit SystemInfo(QObject *parent = nullptr);
~SystemInfo();
// CPU相关
double getCpuUsage(); // 获取总体CPU使用率(百分比)
int getCpuCoreCount(); // 获取CPU核心数
QVector<double> getCpuUsagePerCore(); // 获取每个核心的使用率(可选)
// 内存相关
struct MemoryInfo {
quint64 totalPhysical; // 物理内存总量 (Bytes)
quint64 usedPhysical; // 已用物理内存 (Bytes)
quint64 freePhysical; // 空闲物理内存 (Bytes)
quint64 totalSwap; // 交换分区总量 (Bytes)
quint64 usedSwap; // 已用交换分区 (Bytes)
quint64 freeSwap; // 空闲交换分区 (Bytes)
};
MemoryInfo getMemoryInfo();
// 磁盘相关
QList<QStorageInfo> getDiskInfo(); // 获取磁盘信息列表
private:
// 平台相关的私有数据和方法
#ifdef Q_OS_WIN
// Windows平台私有成员
void* m_pdhQuery;
void* m_pdhTotalCpuCounter;
quint64 m_lastCpuTotalTime;
quint64 m_lastCpuIdleTime;
bool initWindowsCpuCounter();
double calculateCpuUsageWindows();
#endif
#ifdef Q_OS_LINUX
// Linux平台私有成员
QVector<quint64> m_lastCpuTotal;
QVector<quint64> m_lastCpuIdle;
bool readProcStat(quint64 &total, quint64 &idle);
#endif
MemoryInfo m_lastMemInfo;
};
#endif // SYSTEMINFO_H
4.2
systeminfo.cpp
源文件(核心实现)
这里展示关键的平台相关实现。由于篇幅限制,仅展示核心思路和部分代码。
#include "systeminfo.h"
#include <QDebug>
#include <QDir>
#ifdef Q_OS_WIN
#include <windows.h>
#include <pdh.h>
#pragma comment(lib, "pdh.lib")
#endif
#ifdef Q_OS_LINUX
#include <sys/sysinfo.h>
#include <fstream>
#endif
SystemInfo::SystemInfo(QObject *parent) : QObject(parent)
{
// 初始化平台相关资源
#ifdef Q_OS_WIN
m_pdhQuery = nullptr;
m_pdhTotalCpuCounter = nullptr;
m_lastCpuTotalTime = 0;
m_lastCpuIdleTime = 0;
initWindowsCpuCounter();
#endif
#ifdef Q_OS_LINUX
// 初始化CPU时间记录向量
int coreCount = getCpuCoreCount() + 1; // +1 for aggregate “cpu”
m_lastCpuTotal.resize(coreCount);
m_lastCpuIdle.resize(coreCount);
// 读取一次初始值
for(int i=0; i<coreCount; ++i){
// 首次读取,为后续计算差值做准备
quint64 total, idle;
if(readProcStat(total, idle, i)){
m_lastCpuTotal[i] = total;
m_lastCpuIdle[i] = idle;
}
}
#endif
}
SystemInfo::~SystemInfo()
{
#ifdef Q_OS_WIN
if (m_pdhQuery) {
PdhCloseQuery((PDH_HQUERY)m_pdhQuery);
}
#endif
}
// 获取CPU核心数(跨平台)
int SystemInfo::getCpuCoreCount()
{
#ifdef Q_OS_WIN
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwNumberOfProcessors;
#endif
#ifdef Q_OS_LINUX
// 读取 /proc/cpuinfo 中 processor 出现的次数
QFile file("/proc/cpuinfo");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
int count = 0;
while (!in.atEnd()) {
QString line = in.readLine();
if (line.startsWith("processor")) {
count++;
}
}
file.close();
return count > 0 ? count : 1;
}
return 1;
#endif
return 1; // 默认值
}
// 获取总体CPU使用率(跨平台接口)
double SystemInfo::getCpuUsage()
{
#ifdef Q_OS_WIN
return calculateCpuUsageWindows();
#endif
#ifdef Q_OS_LINUX
quint64 total, idle;
if (!readProcStat(total, idle, 0)) { // 索引0代表总的“cpu”行
return 0.0;
}
quint64 totalDiff = total - m_lastCpuTotal[0];
quint64 idleDiff = idle - m_lastCpuIdle[0];
m_lastCpuTotal[0] = total;
m_lastCpuIdle[0] = idle;
if (totalDiff == 0) {
return 0.0;
}
double usage = 100.0 * (1.0 - ((double)idleDiff / totalDiff));
return qBound(0.0, usage, 100.0); // 限制在0-100之间
#endif
return 0.0;
}
// 获取内存信息(跨平台)
SystemInfo::MemoryInfo SystemInfo::getMemoryInfo()
{
MemoryInfo info = {0};
#ifdef Q_OS_WIN
MEMORYSTATUSEX memStat;
memStat.dwLength = sizeof(memStat);
if (GlobalMemoryStatusEx(&memStat)) {
info.totalPhysical = memStat.ullTotalPhys;
info.freePhysical = memStat.ullAvailPhys;
info.usedPhysical = info.totalPhysical - info.freePhysical;
info.totalSwap = memStat.ullTotalPageFile;
info.freeSwap = memStat.ullAvailPageFile;
info.usedSwap = info.totalSwap - info.freeSwap;
}
#endif
#ifdef Q_OS_LINUX
struct sysinfo si;
if (sysinfo(&si) == 0) {
// 注意:sysinfo 返回的单位是 “内存页数”,需要乘以页大小(通常为4KB)
long pageSize = sysconf(_SC_PAGESIZE);
info.totalPhysical = si.totalram * si.mem_unit;
info.freePhysical = si.freeram * si.mem_unit;
info.usedPhysical = info.totalPhysical - info.freePhysical;
info.totalSwap = si.totalswap * si.mem_unit;
info.freeSwap = si.freeswap * si.mem_unit;
info.usedSwap = info.totalSwap - info.freeSwap;
}
#endif
m_lastMemInfo = info; // 可选:缓存上一次结果
return info;
}
// 获取磁盘信息(使用Qt跨平台类)
QList<QStorageInfo> SystemInfo::getDiskInfo()
{
return QStorageInfo::mountedVolumes();
}
// ---------- Windows 平台特定实现 ----------
#ifdef Q_OS_WIN
bool SystemInfo::initWindowsCpuCounter()
{
PDH_STATUS status;
status = PdhOpenQuery(NULL, 0, (PDH_HQUERY*)&m_pdhQuery);
if (status != ERROR_SUCCESS) return false;
// 添加“\Processor(_Total)\% Processor Time”计数器
status = PdhAddCounter((PDH_HQUERY)m_pdhQuery,
TEXT("\\Processor(_Total)\\% Processor Time"),
0,
(PDH_HCOUNTER*)&m_pdhTotalCpuCounter);
return (status == ERROR_SUCCESS);
}
double SystemInfo::calculateCpuUsageWindows()
{
if (!m_pdhQuery || !m_pdhTotalCpuCounter) return 0.0;
PDH_STATUS status;
status = PdhCollectQueryData((PDH_HQUERY)m_pdhQuery);
if (status != ERROR_SUCCESS) return 0.0;
// 等待一小段时间(如500ms)再收集一次数据,计算瞬时使用率
// 注意:更简单的做法是直接读取PDH_FMT_DOUBLE格式的瞬时值,但需要系统支持。
// 这里采用简易的两次采样法。
Sleep(500); // 等待500毫秒
status = PdhCollectQueryData((PDH_HQUERY)m_pdhQuery);
if (status != ERROR_SUCCESS) return 0.0;
PDH_FMT_COUNTERVALUE counterVal;
status = PdhGetFormattedCounterValue((PDH_HCOUNTER)m_pdhTotalCpuCounter,
PDH_FMT_DOUBLE,
NULL,
&counterVal);
if (status == ERROR_SUCCESS && counterVal.CStatus == PDH_CSTATUS_VALID_DATA) {
return counterVal.doubleValue;
}
return 0.0;
}
#endif
// ---------- Linux 平台特定实现 ----------
#ifdef Q_OS_LINUX
bool SystemInfo::readProcStat(quint64 &total, quint64 &idle, int cpuIndex)
{
QFile file("/proc/stat");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QTextStream in(&file);
QString line;
int currentIndex = -1; // “cpu”行索引为-1, “cpu0”为0,以此类推
while (!in.atEnd()) {
line = in.readLine();
if (line.startsWith("cpu")) {
currentIndex++;
if (currentIndex == cpuIndex) {
QStringList parts = line.split(' ', QString::SkipEmptyParts);
if (parts.size() >= 5) { // 至少需要 user, nice, system, idle, iowait
// 总时间 = user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice
// 这里简化计算,取前7个值(到steal为止)之和为total,取第4个(idle)和第5个(iowait)之和为idle
quint64 user = parts[1].toULongLong();
quint64 nice = parts[2].toULongLong();
quint64 system = parts[3].toULongLong();
quint64 idleVal = parts[4].toULongLong();
quint64 iowait = parts.size() > 5 ? parts[5].toULongLong() : 0;
// 可选:加上更多状态
quint64 irq = parts.size() > 6 ? parts[6].toULongLong() : 0;
quint64 softirq = parts.size() > 7 ? parts[7].toULongLong() : 0;
quint64 steal = parts.size() > 8 ? parts[8].toULongLong() : 0;
total = user + nice + system + idleVal + iowait + irq + softirq + steal;
idle = idleVal + iowait;
file.close();
return true;
}
break;
}
} else if (line.startsWith("intr")) {
// 已经过了cpu行,没找到对应的cpuIndex
break;
}
}
file.close();
return false;
}
#endif
5. 集成数据采集、图表展示与主窗口逻辑
5.1 在主窗口中集成SystemInfo和图表
在
mainwindow.h
中,引入必要的头文件并声明成员变量。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTimer>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
QT_CHARTS_USE_NAMESPACE // 使用Qt Charts命名空间
namespace Ui {
class MainWindow;
}
class SystemInfo; // 前向声明
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void updateSystemInfo(); // 定时更新槽函数
private:
Ui::MainWindow *ui;
SystemInfo *m_systemInfo;
QTimer *m_updateTimer;
// 图表相关
QChart *m_cpuChart;
QLineSeries *m_cpuSeries;
QValueAxis *m_axisX;
QValueAxis *m_axisY;
int m_timeIndex; // X轴时间索引
// 数据序列(用于图表历史数据)
QVector<double> m_cpuHistory;
const int HISTORY_LENGTH = 60; // 记录60个数据点(即60秒)
void initChart();
void updateChart(double cpuUsage);
};
#endif // MAINWINDOW_H
在
mainwindow.cpp
中,实现初始化、定时更新和图表刷新。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "systeminfo.h"
#include <QDebug>
#include <QStorageInfo>
#include <QTableWidgetItem>
#include <QHeaderView>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_systemInfo(new SystemInfo(this)),
m_updateTimer(new QTimer(this)),
m_timeIndex(0)
{
ui->setupUi(this);
setWindowTitle(tr("Qt 系统资源监控工具"));
// 初始化图表
initChart();
// 初始化磁盘表格
ui->diskTableWidget->setColumnCount(5);
QStringList headers;
headers << tr("盘符/挂载点") << tr("总容量") << tr("已用") << tr("可用") << tr("使用率");
ui->diskTableWidget->setHorizontalHeaderLabels(headers);
ui->diskTableWidget->horizontalHeader()->setStretchLastSection(true);
// 连接定时器
connect(m_updateTimer, &QTimer::timeout, this, &MainWindow::updateSystemInfo);
m_updateTimer->start(1000); // 每秒更新一次
// 立即更新一次
updateSystemInfo();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::initChart()
{
m_cpuChart = new QChart();
m_cpuSeries = new QLineSeries();
m_cpuSeries->setName(tr("CPU使用率 (%)"));
m_cpuChart->addSeries(m_cpuSeries);
m_cpuChart->setTitle(tr("CPU使用率实时监控"));
m_cpuChart->setAnimationOptions(QChart::NoAnimation); // 禁用动画以获得更好性能
m_axisX = new QValueAxis;
m_axisX->setRange(0, HISTORY_LENGTH - 1);
m_axisX->setLabelFormat("%d");
m_axisX->setTitleText(tr("时间 (秒前)"));
// 隐藏X轴具体数值标签,因为我们用“秒前”表示
m_axisX->setTickCount(6); // 显示6个刻度
m_cpuChart->addAxis(m_axisX, Qt::AlignBottom);
m_cpuSeries->attachAxis(m_axisX);
m_axisY = new QValueAxis;
m_axisY->setRange(0, 100);
m_axisY->setTitleText(tr("使用率 (%)"));
m_cpuChart->addAxis(m_axisY, Qt::AlignLeft);
m_cpuSeries->attachAxis(m_axisY);
// 将图表设置到UI中的QChartView
ui->chartView->setChart(m_cpuChart);
ui->chartView->setRenderHint(QPainter::Antialiasing);
// 初始化历史数据
m_cpuHistory.fill(0.0, HISTORY_LENGTH);
}
void MainWindow::updateSystemInfo()
{
// 1. 更新CPU信息
double cpuUsage = m_systemInfo->getCpuUsage();
ui->cpuProgressBar->setValue(static_cast<int>(cpuUsage));
ui->labelCpuUsage->setText(QString("%1%").arg(cpuUsage, 0, 'f', 1));
// 2. 更新内存信息
SystemInfo::MemoryInfo memInfo = m_systemInfo->getMemoryInfo();
if (memInfo.totalPhysical > 0) {
double memUsagePercent = 100.0 * memInfo.usedPhysical / memInfo.totalPhysical;
ui->memProgressBar->setValue(static_cast<int>(memUsagePercent));
ui->labelMemUsage->setText(QString("%1% (%2 / %3)")
.arg(memUsagePercent, 0, 'f', 1)
.arg(formatBytes(memInfo.usedPhysical))
.arg(formatBytes(memInfo.totalPhysical)));
}
// 更新交换空间(如果有)
if (memInfo.totalSwap > 0) {
double swapUsagePercent = 100.0 * memInfo.usedSwap / memInfo.totalSwap;
ui->swapProgressBar->setValue(static_cast<int>(swapUsagePercent));
ui->labelSwapUsage->setText(QString("%1%").arg(swapUsagePercent, 0, 'f', 1));
} else {
ui->swapProgressBar->setValue(0);
ui->labelSwapUsage->setText(tr("N/A"));
}
// 3. 更新磁盘信息
QList<QStorageInfo> disks = m_systemInfo->getDiskInfo();
ui->diskTableWidget->setRowCount(disks.size());
for (int i = 0; i < disks.size(); ++i) {
const QStorageInfo &disk = disks.at(i);
qint64 total = disk.bytesTotal();
qint64 free = disk.bytesFree();
qint64 used = total - free;
double usagePercent = total > 0 ? (100.0 * used / total) : 0.0;
ui->diskTableWidget->setItem(i, 0, new QTableWidgetItem(disk.rootPath()));
ui->diskTableWidget->setItem(i, 1, new QTableWidgetItem(formatBytes(total)));
ui->diskTableWidget->setItem(i, 2, new QTableWidgetItem(formatBytes(used)));
ui->diskTableWidget->setItem(i, 3, new QTableWidgetItem(formatBytes(free)));
ui->diskTableWidget->setItem(i, 4, new QTableWidgetItem(QString("%1%").arg(usagePercent, 0, 'f', 1)));
}
// 4. 更新图表
updateChart(cpuUsage);
// 5. 更新状态栏
ui->statusBar->showMessage(tr("最后更新: %1").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
}
void MainWindow::updateChart(double cpuUsage)
{
// 将新数据点添加到历史记录
m_cpuHistory.append(cpuUsage);
if (m_cpuHistory.size() > HISTORY_LENGTH) {
m_cpuHistory.removeFirst();
}
// 清空系列并重新添加所有点
m_cpuSeries->clear();
for (int i = 0; i < m_cpuHistory.size(); ++i) {
m_cpuSeries->append(i, m_cpuHistory.at(i));
}
// 更新X轴范围,使其看起来是滚动的
if (m_cpuHistory.size() >= HISTORY_LENGTH) {
m_axisX->setRange(0, HISTORY_LENGTH - 1);
} else {
m_axisX->setRange(0, m_cpuHistory.size() - 1);
}
}
// 辅助函数:格式化字节数为易读单位 (KB, MB, GB, TB)
QString MainWindow::formatBytes(qint64 bytes)
{
const qint64 kb = 1024;
const qint64 mb = kb * 1024;
const qint64 gb = mb * 1024;
const qint64 tb = gb * 1024;
if (bytes >= tb) {
return QString("%1 TB").arg(bytes / (double)tb, 0, 'f', 2);
} else if (bytes >= gb) {
return QString("%1 GB").arg(bytes / (double)gb, 0, 'f', 2);
} else if (bytes >= mb) {
return QString("%1 MB").arg(bytes / (double)mb, 0, 'f', 2);
} else if (bytes >= kb) {
return QString("%1 KB").arg(bytes / (double)kb, 0, 'f', 2);
} else {
return QString("%1 B").arg(bytes);
}
}
5.2 编译与运行
完成以上代码后,点击Qt Creator左下角的绿色运行按钮(或按
Ctrl+R
)进行编译和运行。如果一切配置正确,你将看到一个窗口,其中包含动态更新的CPU/内存进度条、磁盘信息表格以及一个实时绘制CPU使用率曲线的图表。
6. 常见问题排查与解决方案
在开发过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 检查与解决方案 |
|---|---|---|
编译错误:
error: ‘QChart’ does not name a type
|
未在
.pro
文件中添加
QT += charts
,或未包含正确的头文件。
|
1. 检查
.pro
文件是否有
QT += charts
。
2. 在头文件中使用
#include <QtCharts/QChart>
或
QT_CHARTS_USE_NAMESPACE
。
|
编译错误:
undefined reference to ...
(链接错误)
|
未链接必要的系统库(如Windows下的
pdh.lib
)。
|
在
.pro
文件中使用
win32 { LIBS += -lpdh }
添加库链接。
|
| 程序运行后CPU/内存数据显示为0或不变 |
1. 平台相关代码未正确编译(条件编译错误)。
2. 系统API调用失败或权限不足。 3. 定时器未启动或槽函数未连接。 |
1. 使用
#ifdef Q_OS_WIN
和
#ifdef Q_OS_LINUX
确保代码在正确平台编译。
2. 在
getCpuUsage
等函数开始处添加
qDebug()
输出,查看返回值。
3. 检查
m_updateTimer->start(1000)
是否执行,以及
connect
是否成功。
|
Linux下读取
/proc/stat
失败
| 文件路径错误或权限问题。 |
确保程序有读取
/proc/stat
的权限(通常有)。使用
QFile
的
errorString()
打印具体错误。
|
| Windows下PDH计数器初始化失败 | PDH库未正确加载或计数器路径错误。 |
检查
PdhOpenQuery
和
PdhAddCounter
的返回值。在Windows 10/11上,计数器路径通常是正确的。
|
| 图表不显示或显示异常 |
1.
QChartView
未正确提升。
2. 图表系列未关联到坐标轴。 3. 坐标轴范围设置不当。 |
1. 在Qt Designer中确认
QChartView
控件已正确提升。
2. 在代码中确认
series->attachAxis(axis)
被调用。
3. 检查
axisX->setRange()
设置的值是否合理。
|
| 程序在Windows打包后无法在其他电脑运行 | 缺少Qt运行时库或VC++运行库。 |
使用
windeployqt
工具自动打包依赖,并确保目标电脑安装了对应版本的
Visual C++ Redistributable
。
|
注意 :跨平台代码的调试是关键。务必在目标平台上进行测试。在Linux下,可以使用
strace或gdb跟踪系统调用;在Windows下,可以使用Process Monitor或调试器查看API调用情况。
7. 项目打包与部署
7.1 Windows平台打包
-
使用Release模式编译
:在Qt Creator中,将构建套件切换到
Release模式并重新编译。 -
找到可执行文件
:在项目构建目录的
release文件夹下找到.exe文件。 -
使用windeployqt
:打开Qt命令行(开始菜单 -> Qt -> 你的版本 -> Qt命令行),导航到
.exe文件所在目录,执行:
此命令会自动将所需的Qt DLL、插件等复制到当前目录。windeployqt YourAppName.exe -
检查VC++运行库
:确保目标计算机安装了对应版本的Microsoft Visual C++ Redistributable。可以将必要的
vc_redist.x64.exe(或x86)与你的程序一起分发。 -
测试
:将整个文件夹复制到一台没有Qt开发环境的Windows电脑上,运行
.exe文件,检查是否正常。
7.2 Linux平台打包
Linux下打包相对复杂,常见方法有:
-
分发AppImage
:使用
linuxdeployqt工具生成一个独立的AppImage文件。 -
制作DEB/RPM包
:为你的程序创建
.desktop文件、图标,并编写control文件,使用dpkg-deb或rpmbuild打包。 - 静态编译 :使用静态链接的Qt库进行编译,生成不依赖系统Qt的动态链接库。但这需要从源码编译静态Qt,过程较复杂。
对于课程设计,最简单的方式是提供源代码和编译说明(
README.md
),要求用户自行安装Qt开发环境后编译。
8. 扩展方向与最佳实践
完成基础监控后,你可以考虑以下扩展,让项目更加完善:
- 监控更多指标 :网络流量(发送/接收)、GPU使用率、单个进程的资源消耗、电池状态(笔记本)、传感器温度等。
-
优化图表性能
:当历史数据点很多时,
QLineSeries的append操作可能变慢。可以考虑使用QLineSeries::replace()来更新数据,或使用QScatterSeries只显示当前点。 -
添加配置功能
:允许用户自定义刷新频率、选择要监控的磁盘、设置图表颜色和样式、选择是否开机启动等。可以使用
QSettings来保存配置。 -
实现日志记录
:将资源使用情况定期记录到文件中,便于后续分析。可以使用
QFile和QTextStream,注意避免频繁写文件影响性能。 -
设置资源告警
:当CPU或内存使用率超过某个阈值时,通过系统通知(如
QSystemTrayIcon)或改变界面颜色来提醒用户。 -
代码结构优化
:将数据获取、数据处理、UI更新进一步解耦,使用信号槽机制或
QThread将耗时的数据采集放在子线程中,避免阻塞UI主线程导致界面卡顿。
最佳实践建议 :
-
错误处理
:所有文件操作(如打开
/proc/stat)和系统API调用都应检查返回值,并进行适当的错误处理或提供默认值。 - 资源释放 :在类的析构函数中正确释放平台相关的资源句柄(如Windows的PDH查询句柄)。
- 性能考量 :数据采集频率不宜过高(通常1-2秒一次足够),避免不必要的系统开销。
-
用户体验
:对于长时间运行的监控工具,添加一个系统托盘图标(
QSystemTrayIcon)是很好的做法,允许用户最小化到托盘。 - 版本管理 :使用Git等工具管理你的源代码,特别是当你尝试不同的跨平台实现方案时。

1004

被折叠的 条评论
为什么被折叠?



