分析一个跨平台QT项目(获取系统CPU使用率和内存占用率)
如果是仅仅做界面的话,Qt框架本身就是跨平台的,不需要额外的操作。但是如果涉及到一些和平台自身特性相关的操作的时候,这部分代码就无法实现跨平台了。针对平台特性相关的代码,我们采用宏定义和qmake,在不同的平台下执行不同的代码,从而实现跨平台。这里介绍一下,如何通过宏定义和qmake,实现项目的跨平台。
下面以一个获取系统CPU使用率和内存使用率应用为例,说明一下设计思路。例子中主要采用了两种设计模式:策略模式和单例模式。
通过采用策略模式,我们抽离出应用和系统相关的接口,然后通过子类化,添加系统接口在不同平台下的实现。类图如下所示:
SysInfo中抽离出了系统访问的接口,供Qt框架调用,在Windows系统中执行SysInfoWindowsImpl中的函数,在Linux系统中执行SysInfoLinuxImpl中的函数,在Mac系统中执行SysInfoMacImpl中的函数。我们将三个子类分别放置于三个不同的文件中,确保在不同的系统中只会编译对应平台的内容。
系统信息访问类的公共接口如下:
//sysinfo.h#ifndef SYSINFO_H#define SYSINFO_Hclass SysInfo{public: //构造和系统函数 static SysInfo& instance(); virtual ~SysInfo(); //初始化函数 virtual void init() = 0; //cpu平均使用率 virtual double cpuLoadAverage() = 0; //内存使用率 virtual double memoryUsed() = 0;protected: explicit SysInfo();private: SysInfo(const SysInfo& rhs); SysInfo& operator=(const SysInfo& rhs);};#endif // SYSINFO_H
在各个平台下接口的实现子类如下:
windows平台接口实现
//syteminfowindowsimpl.h//针对windows平台的接口#ifndef SYTEMINFOWINDOWSIMPL_H#define SYTEMINFOWINDOWSIMPL_H#include #include #include "SysInfo.h"typedef struct _FILETIME FILETIME;class SysInfoWindowsImpl : public SysInfo{public: SysInfoWindowsImpl(); void init() override; //windows下的实现 double cpuLoadAverage() override; //windows下的实现 double memoryUsed() override;private: //获取并记录CPU的原始数据 QVector cpuRawData(); //转换时间格式 qulonglong convertFileTime(const FILETIME& filetime) const;private: QVector mCpuLoadLastValues;};#endif // SYTEMINFOWINDOWSIMPL_H
//syteminfowindowsimpl.cpp#include "syteminfowindowsimpl.h"#include SysInfoWindowsImpl::SysInfoWindowsImpl() : SysInfo(),mCpuLoadLastValues(){}double SysInfoWindowsImpl::memoryUsed(){ //获取内存的使用率 MEMORYSTATUSEX memoryStatus; memoryStatus.dwLength = sizeof(MEMORYSTATUSEX); GlobalMemoryStatusEx(&memoryStatus); qulonglong memoryPhysicalUsed = memoryStatus.ullTotalPhys - memoryStatus.ullAvailPhys; return (double)memoryPhysicalUsed / (double)memoryStatus.ullTotalPhys * 100.0;}void SysInfoWindowsImpl::init(){ mCpuLoadLastValues = cpuRawData();}QVector SysInfoWindowsImpl::cpuRawData(){ //获取CPU的占用率 //闲置时间 FILETIME idleTime; //内核使用时间 FILETIME kernelTime; //用户使用时间 FILETIME userTime; GetSystemTimes(&idleTime, &kernelTime, &userTime); QVector rawData; rawData.append(convertFileTime(idleTime)); rawData.append(convertFileTime(kernelTime)); rawData.append(convertFileTime(userTime)); return rawData;}qulonglong SysInfoWindowsImpl::convertFileTime(const FILETIME& filetime) const{ ULARGE_INTEGER largeInteger; largeInteger.LowPart = filetime.dwLowDateTime; largeInteger.HighPart = filetime.dwHighDateTime; return largeInteger.QuadPart;}double SysInfoWindowsImpl::cpuLoadAverage(){ QVector firstSample = mCpuLoadLastValues; QVector secondSample = cpuRawData(); mCpuLoadLastValues = secondSample; //获取两个时间点之间的CPU时间 qulonglong currentIdle = secondSample[0] - firstSample[0]; qulonglong currentKernel = secondSample[1] - firstSample[1]; qulonglong currentUser = secondSample[2] - firstSample[2]; qulonglong currentSystem = currentKernel + currentUser; //(总的时间 - 空闲时间)/ 总的时间 = 占用cpu的时间,也就是占用率 double percent = (currentSystem - currentIdle) * 100.0 / currentSystem ; return qBound(0.0, percent, 100.0);}
Linux平台接口实现
//sysinfolinuximpl.h#ifndef SYSINFOLINUXIMPL_H#define SYSINFOLINUXIMPL_H#include #include #include "sysinfo.h"class SysInfoLinuxImpl : public SysInfo{public: SysInfoLinuxImpl(); void init() override; double cpuLoadAverage() override; double memoryUsed() override;private: QVector cpuRawData();private: QVector mCpuLoadLastValues;};#endif // SYSINFOLINUXIMPL_H
//sysinfolinuximpl.cpp#include "sysinfolinuximpl.h"#include #include #include SysInfoLinuxImpl::SysInfoLinuxImpl() : SysInfo(), mCpuLoadLastValues(){}void SysInfoLinuxImpl::init(){ mCpuLoadLastValues = cpuRawData();}double SysInfoLinuxImpl::memoryUsed(){ struct sysinfo memInfo; sysinfo(&memInfo); qulonglong totalMemory = memInfo.totalram; totalMemory += memInfo.totalswap; totalMemory *= memInfo.mem_unit; qulonglong totalMemoryUsed = memInfo.totalram - memInfo.freeram; totalMemoryUsed += memInfo.totalswap - memInfo.freeswap; totalMemoryUsed *= memInfo.mem_unit; double percent = (double)totalMemoryUsed / (double)totalMemory * 100.0; return qBound(0.0, percent, 100.0);}QVector SysInfoLinuxImpl::cpuRawData(){ QFile file("/proc/stat"); file.open(QIODevice::ReadOnly); QByteArray line = file.readLine(); file.close(); qulonglong totalUser = 0, totalUserNice = 0, totalSystem = 0, totalIdle = 0; std::sscanf(line.data(), "cpu %llu %llu %llu %llu", &totalUser, &totalUserNice, &totalSystem, &totalIdle); QVector rawData; rawData.append(totalUser); rawData.append(totalUserNice); rawData.append(totalSystem); rawData.append(totalIdle); return rawData;}double SysInfoLinuxImpl::cpuLoadAverage(){ QVector firstSample = mCpuLoadLastValues; QVector secondSample = cpuRawData(); mCpuLoadLastValues = secondSample; double overall = (secondSample[0] - firstSample[0]) + (secondSample[1] - firstSample[1]) + (secondSample[2] - firstSample[2]); //(用户时间 + 系统时间)/ 总的时间 = 占用cpu的时间,也就是占用率 double total = overall + (secondSample[3] - firstSample[3]); double percent = (overall / total) * 100.0; return qBound(0.0, percent, 100.0);}
Mac平台接口实现
//sysinfomacimpl.h#ifndef SYSINFOMACIMPL_H#define SYSINFOMACIMPL_H#include "sysinfo.h"#include #include class SysInfoMacImpl : public SysInfo{public: SysInfoMacImpl(); void init() override; double cpuLoadAverage() override; double memoryUsed() override;private: QVector cpuRawData();private: QVector mCpuLoadLastValues;};#endif // SYSINFOMACIMPL_H
//sysinfomacimpl.cpp#include "sysinfomacimpl.h"#include #include #include #include #include SysInfoMacImpl::SysInfoMacImpl() :SysInfo(){}double SysInfoMacImpl::memoryUsed(){ vm_size_t pageSize; vm_statistics64_data_t vmStats; mach_port_t machPort = mach_host_self(); mach_msg_type_number_t count = sizeof(vmStats) / sizeof(natural_t); host_page_size(machPort, &pageSize); host_statistics64(machPort, HOST_VM_INFO, (host_info64_t)&vmStats, &count); qulonglong freeMemory = (int64_t)vmStats.free_count * (int64_t)pageSize; qulonglong totalMemoryUsed = ((int64_t)vmStats.active_count + (int64_t)vmStats.inactive_count + (int64_t)vmStats.wire_count)* (int64_t)pageSize; qulonglong totalMemory = freeMemory + totalMemoryUsed; double percent = (double)totalMemoryUsed / (double)totalMemory * 100.0; return qBound(0.0, percent, 100.0);}void SysInfoMacImpl::init(){ mCpuLoadLastValues = cpuRawData();}QVector SysInfoMacImpl::cpuRawData(){ host_cpu_load_info_data_t cpuInfo; mach_msg_type_number_t cpuCount = HOST_CPU_LOAD_INFO_COUNT; QVector rawData; qulonglong totalUser = 0, totalUserNice = 0, totalSystem = 0, totalIdle = 0; host_statistics(mach_host_self(),HOST_CPU_LOAD_INFO,(host_info_t)&cpuInfo, &cpuCount); for(unsigned int i = 0; i < cpuCount; i++) { unsigned int maxTicks = CPU_STATE_MAX * i; totalUser += cpuInfo.cpu_ticks[maxTicks + CPU_STATE_USER]; totalUserNice += cpuInfo.cpu_ticks[maxTicks + CPU_STATE_SYSTEM]; totalSystem += cpuInfo.cpu_ticks[maxTicks + CPU_STATE_NICE]; totalIdle += cpuInfo.cpu_ticks[maxTicks + CPU_STATE_IDLE]; } rawData.append(totalUser); rawData.append(totalUserNice); rawData.append(totalSystem); rawData.append(totalIdle); return rawData;}
通过在pro文件中添加平台对应的宏操作,确保在执行qmake的时候只有对应平台的接口实现参与编译,pro文件中的宏添加方法如下:
windows { SOURCES += syteminfowindowsimpl.cpp HEADERS += syteminfowindowsimpl.h}linux { SOURCES += sysinfolinuximpl.cpp HEADERS += sysinfolinuximpl.h}macx { SOURCES += sysinfomacimpl.cpp HEADERS += sysinfomacimpl.h}
在文件编译层我们可以通过qmake的宏实现在不同的平台上编译不同的文件,在代码层我们可以通过Qt系统的宏来保障不同的平台下执行不同的代码。
下面我们引入平台宏和单例模式确保在不同的平台下调用不同的类,实现逻辑如下:
#include "sysinfo.h"#include #ifdef Q_OS_WIN#include "syteminfowindowsimpl.h"#elif defined(Q_OS_MAC)#include "sysinfomacimpl.h"#elif defined(Q_OS_LINUX)#include "sysinfolinuximpl.h"#endifSysInfo& SysInfo::instance(){#ifdef Q_OS_WIN static SysInfoWindowsImpl singleton;#elif defined(Q_OS_MAC) static SysInfoMacImpl singleton;#elif defined(Q_OS_LINUX) static SysInfoLinuxImpl singleton;#endif return singleton;}SysInfo::SysInfo(){}SysInfo::~SysInfo(){}
通过引入qmake宏和Qt框架的宏,我们就可以获取CPU使用率和内存占用率的跨平台接口了。接下来通过Qt的QChartView控件动态显示CPU的占用率和内存的使用率。
首先抽离出控件的公共类如下:
//sysinfowidget.h#ifndef SYSINFOWIDGET_H#define SYSINFOWIDGET_H#include #include #include class SysInfoWidget : public QWidget{ Q_OBJECTpublic: //@1父控件 @2控件延时 @3控件的刷新事件 explicit SysInfoWidget(QWidget *parent = 0, int startDelayMs = 500, int updateSeriesDelayMs = 500);protected: //获取公共的QtCharView控件 QtCharts::QChartView& chartView();protected slots: //刷新控件 virtual void updateSeries() = 0;private: QTimer mRefreshTimer; QtCharts::QChartView mChartView;};#endif // SYSINFOWIDGET_H
//sysinfowidget.cpp#include "sysinfowidget.h"#include using namespace QtCharts;SysInfoWidget::SysInfoWidget(QWidget *parent, int startDelayMs, int updateSeriesDelayMs) : QWidget(parent), mChartView(this){ //初始化定时器和控件基本样式 mRefreshTimer.setInterval(updateSeriesDelayMs); connect(&mRefreshTimer, &QTimer::timeout, this, &SysInfoWidget::updateSeries); QTimer::singleShot(startDelayMs, [this] { mRefreshTimer.start(); }); mChartView.setRenderHint(QPainter::Antialiasing); mChartView.chart()->legend()->setVisible(false); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(&mChartView); setLayout(layout);}QChartView& SysInfoWidget::chartView(){ return mChartView;}
CPU使用率控件实现
//cpuwidget.h#ifndef CPUWIDGET_H#define CPUWIDGET_H#include #include "sysinfowidget.h"class CpuWidget : public SysInfoWidget{ Q_OBJECTpublic: explicit CpuWidget(QWidget* parent = 0);protected slots: void updateSeries() override;private: QtCharts::QPieSeries* mSeries;};#endif // CPUWIDGET_H
cpuwidget.cpp#include "cpuwidget.h"#include "sysinfo.h"using namespace QtCharts;CpuWidget::CpuWidget(QWidget* parent) : SysInfoWidget(parent), mSeries(new QPieSeries(this)){ //以饼状图显示CPU的使用率 mSeries->setHoleSize(0.35); mSeries->append("CPU Load", 30.0); mSeries->append("CPU Free", 70.0); QChart* chart = chartView().chart(); chart->addSeries(mSeries); chart->setTitle("CPU average load");}void CpuWidget::updateSeries(){ //动态刷新CPU的使用率 double cpuLoadAverage = SysInfo::instance().cpuLoadAverage(); mSeries->clear(); mSeries->append("Load", cpuLoadAverage); mSeries->append("Free", 100.0 - cpuLoadAverage);}
内存占用率控件实现
//memorywidget.h#ifndef MEMORYWIDGET_H#define MEMORYWIDGET_H#include #include "sysinfowidget.h"class MemoryWidget : public SysInfoWidget{ Q_OBJECTpublic: explicit MemoryWidget(QWidget *parent = 0);protected slots: void updateSeries() override; //以曲线图动态显示内存的占用率private: QtCharts::QLineSeries* mSeries; qint64 mPointPositionX;};#endif // MEMORYWIDGET_H
//memorywidget.cpp#include "memorywidget.h"#include #include #include #include "sysinfo.h"using namespace QtCharts;const int CHART_X_RANGE_COUNT = 50;const int CHART_X_RANGE_MAX = CHART_X_RANGE_COUNT - 1;//渐变色的起始色和终止色const int COLOR_DARK_BLUE = 0x209fdf;const int COLOR_LIGHT_BLUE = 0xbfdfef;const int PEN_WIDTH = 3;MemoryWidget::MemoryWidget(QWidget *parent) : SysInfoWidget(parent), mSeries(new QLineSeries(this)), mPointPositionX(0){ QPen pen(COLOR_DARK_BLUE); pen.setWidth(PEN_WIDTH); QLinearGradient gradient(QPointF(0, 0), QPointF(0, 1)); gradient.setColorAt(1.0, COLOR_DARK_BLUE); gradient.setColorAt(0.0, COLOR_LIGHT_BLUE); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); //动态刷线曲线图 QAreaSeries* areaSeries = new QAreaSeries(mSeries); areaSeries->setPen(pen); areaSeries->setBrush(gradient); QChart* chart = chartView().chart(); chart->addSeries(areaSeries); chart->setTitle("Memory used"); chart->createDefaultAxes(); chart->axisX()->setRange(0, CHART_X_RANGE_MAX); chart->axisX()->setVisible(false); chart->axisY()->setRange(0, 100);}void MemoryWidget::updateSeries(){ //刷线内存使用率 double memoryUsed = SysInfo::instance().memoryUsed(); mSeries->append(mPointPositionX++, memoryUsed); //超出显示范围之后往前滚动一个格子 if (mSeries->count() > CHART_X_RANGE_COUNT) { QChart* chart = chartView().chart(); chart->scroll(chart->plotArea().width() / CHART_X_RANGE_MAX, 0); mSeries->remove(0); }}
设计完成两个控件之后,我们在主窗口中添加两个对应的控件进行显示。
//mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include #include "cpuwidget.h"#include "memorywidget.h"namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: explicit MainWindow(QWidget *parent = 0); ~MainWindow();private: Ui::MainWindow *ui; CpuWidget mCpuWidget; MemoryWidget mMemoryWidget;};#endif // MAINWINDOW_H
//mainwindow.cpp#include "mainwindow.h"#include "ui_MainWindow.h"#include #include "sysinfo.h"MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), mCpuWidget(this), mMemoryWidget(this){ ui->setupUi(this); SysInfo::instance().init(); ui->centralWidget->layout()->addWidget(&mCpuWidget); ui->centralWidget->layout()->addWidget(&mMemoryWidget);}MainWindow::~MainWindow(){ delete ui;}
显示效果
到此为止一个跨平台显示系统CPU使用率和内存占用率的程序就完成了,项目运行的时候效果如下:
总结一下吧
1.Qt项目中文件层的跨平台可以通过qmake的宏在pro工程文件中实现
2.代码层次的跨平台可以通过Qt框架中的系统来实现
3.通过策略模式+单例模式,我们可以实现对应接口在不同平台下执行不同的代码
4.动态显示曲线图饼状图一类的视图的时候可以采用Qt框架中的QChartView模块
参考资料: 《End-to-End-GUI-development-with-Qt5》提供的Demo