> 文档中心 > 分析一个跨平台QT项目(获取系统CPU使用率和内存占用率)

分析一个跨平台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

 

解梦吧