如果是仅仅做界面的话,Qt框架本身就是跨平台的,不需要额外的操作。但是如果涉及到一些和平台自身特性相关的操作的时候,这部分代码就无法实现跨平台了。针对平台特性相关的代码,我们采用宏定义和qmake,在不同的平台下执行不同的代码,从而实现跨平台。这里介绍一下,如何通过宏定义和qmake,实现项目的跨平台。
下面以一个获取系统CPU使用率和内存使用率应用为例,说明一下设计思路。例子中主要采用了两种设计模式:策略模式和单例模式。
通过采用策略模式,我们抽离出应用和系统相关的接口,然后通过子类化,添加系统接口在不同平台下的实现。类图如下所示:
SysInfo中抽离出了系统访问的接口,供Qt框架调用,在Windows系统中执行SysInfoWindowsImpl中的函数,在Linux系统中执行SysInfoLinuxImpl中的函数,在Mac系统中执行SysInfoMacImpl中的函数。我们将三个子类分别放置于三个不同的文件中,确保在不同的系统中只会编译对应平台的内容。
系统信息访问类的公共接口如下:
//sysinfo.h
#ifndef SYSINFO_H
#define SYSINFO_H
class 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 <QtGlobal>
#include <QVector>
#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<qulonglong> cpuRawData();
//转换时间格式
qulonglong convertFileTime(const FILETIME& filetime) const;
private:
QVector<qulonglong> mCpuLoadLastValues;
};
#endif // SYTEMINFOWINDOWSIMPL_H
//syteminfowindowsimpl.cpp
#include "syteminfowindowsimpl.h"
#include <windows.h>
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<qulonglong> SysInfoWindowsImpl::cpuRawData()
{
//获取CPU的占用率
//闲置时间
FILETIME idleTime;
//内核使用时间
FILETIME kernelTime;
//用户使用时间
FILETIME userTime;
GetSystemTimes(&idleTime, &kernelTime, &userTime);
QVector<qulonglong> 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<qulonglong> firstSample = mCpuLoadLastValues;
QVector<qulonglong> 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 <QtGlobal>
#include <QVector>
#include "sysinfo.h"
class SysInfoLinuxImpl : public SysInfo
{
public:
SysInfoLinuxImpl();
void init() override;
double cpuLoadAverage() override;
double memoryUsed() override;
private:
QVector<qulonglong> cpuRawData();
private:
QVector<qulonglong> mCpuLoadLastValues;
};
#endif // SYSINFOLINUXIMPL_H
//sysinfolinuximpl.cpp
#include "sysinfolinuximpl.h"
#include <sys/types.h>
#include <sys/sysinfo.h>
#include <QFile>
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<qulonglong> 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<qulonglong> rawData;
rawData.append(totalUser);
rawData.append(totalUserNice);
rawData.append(totalSystem);
rawData.append(totalIdle);
return rawData;
}
double SysInfoLinuxImpl::cpuLoadAverage()
{
QVector<qulonglong> firstSample = mCpuLoadLastValues;
QVector<qulonglong> 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 <QtGlobal>
#include <QVector>
class SysInfoMacImpl : public SysInfo
{
public:
SysInfoMacImpl();
void init() override;
double cpuLoadAverage() override;
double memoryUsed() override;
private:
QVector<qulonglong> cpuRawData();
private:
QVector<qulonglong> mCpuLoadLastValues;
};
#endif // SYSINFOMACIMPL_H
//sysinfomacimpl.cpp
#include "sysinfomacimpl.h"
#include <mach/vm_statistics.h>
#include <mach/mach_types.h>
#include <mach/mach_init.h>
#include <mach/mach_host.h>
#include <mach/vm_map.h>
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<qulonglong> SysInfoMacImpl::cpuRawData()
{
host_cpu_load_info_data_t cpuInfo;
mach_msg_type_number_t cpuCount = HOST_CPU_LOAD_INFO_COUNT;
QVector<qulonglong> 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 <QtGlobal>
#ifdef Q_OS_WIN
#include "syteminfowindowsimpl.h"
#elif defined(Q_OS_MAC)
#include "sysinfomacimpl.h"
#elif defined(Q_OS_LINUX)
#include "sysinfolinuximpl.h"
#endif
SysInfo& 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 <QWidget>
#include <QTimer>
#include <QtCharts/QChartView>
class SysInfoWidget : public QWidget
{
Q_OBJECT
public:
//@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 <QVBoxLayout>
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 <QtCharts/QPieSeries>
#include "sysinfowidget.h"
class CpuWidget : public SysInfoWidget
{
Q_OBJECT
public:
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 <QtCharts/QLineSeries>
#include "sysinfowidget.h"
class MemoryWidget : public SysInfoWidget
{
Q_OBJECT
public:
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 <QtCharts/QAreaSeries>
#include <QPen>
#include <QLinearGradient>
#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 <QMainWindow>
#include "cpuwidget.h"
#include "memorywidget.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
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 <QDebug>
#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
?
|