构建一个跨平台的应用(Create A Cross-Platform Application)
目录
构建一个跨平台的应用(Create A Cross-Platform Application)
设计模式
开始构建
Qt是跨平台的C++框架,这里,我们将会构建一个简单的C++跨平台项目来熟悉QT是如何实现简单的跨平台的。
我们将要构建的是一个动态查看,不同操作系统下操作系统运行时的一些状态,这就是一个跨平台的需求。
设计模式
要从 OS(操作系统)检索 CPU 和内存使用情况,我们将使用一些特定于平台的代码。 为了成功完成此任务,我们将使用两种设计模式:
-
策略模式:这是一个描述功能的接口(例如,检索 CPU 使用情况),特定行为(在 Windows/macOS/Linux 上检索 CPU 使用情况)将在实现此接口的子类中执行。
-
pIMPL法:pointer to implements方法
-
单例模式:此模式保证给定类只有一个实例。此实例将通过唯一访问点轻松访问。
-
工厂模式:返回对应子类的实例

我们将会使用一个单例:System_Info,这是可以理解的因为我们的应用软件只会跑在一个操作系统上,因此没有必要创建多个实例,这种方式广泛的应用在那些维护全局状态的对象当中,也是一个常见的设计模式了。
开始构建
我们刚刚大致的想法就是如此,现在我们开始构建代码。我们仔细思考,作为用户,他只会关心接口的功能而不在乎里面的实现。为此,我们需要做的是使用pIMPL法,把设计到具体的项目平台的代码给他封装起来,这样可以减轻我们的管理负担。
因此,我们先带有尝试性质的——设计这些必要的接口:
#ifndef SYSTEM_INFOIMPL_H
#define SYSTEM_INFOIMPL_H
#include <QtClassHelperMacros>
/*
The Actual Action class
All Bases For Every Possible Operating
System
*/
class System_InfoImpl {
public:
System_InfoImpl() = default;
Q_DISABLE_COPY(System_InfoImpl);
virtual ~System_InfoImpl() = default;
// Impl Interfaces
virtual void _initWork() = 0;
virtual double _cpuLoadAverage() = 0;
virtual double _memoryUsed() = 0;
};
#endif // SYSTEM_INFOIMPL_H
可以看到,我们的IMPL接口自身也被抽象为接口类,这是合理的——每一个操作系统获取内存和CPU的状态都不一样,需要我们更加具体的类实现。
这样,我们的System_Info这个前端接口,就只是单纯的转发请求到System_InfoImpl类中,这个类在不同的平台被初始化为不同的类中去了。
IMPL方法具备这些优点,对于那些强调不必关心内部实现的方法中:
降低耦合
信息隐藏
降低编译依赖,提高编译速度
接口与实现分离
下面,我们就来看看Windows平台下的如何实现
#ifndef SYSTEM_INFOWINDOWSIMPL_H
#define SYSTEM_INFOWINDOWSIMPL_H
#include <QList>
#include <array>
#include "System_InfoImpl.h"
using FILETIME = struct _FILETIME; // Windows平台下的FILETIME接口
class System_InfoWindowsImpl : public System_InfoImpl {
public:
System_InfoWindowsImpl();
Q_DISABLE_COPY(System_InfoWindowsImpl);
~System_InfoWindowsImpl() = default;
// Impls
void _initWork() override;
double _cpuLoadAverage() override;
double _memoryUsed() override;
struct WindowsTools {
// 辅助函数
static qulonglong fromWindowsFileTime(const FILETIME& fileTime);
};
private:
struct Labels {
enum class Label { IDLE = 0, KERN = 1, USER = 2, SIZE };
static constexpr short to_index(Label l) {
return static_cast<short>(l);
}
};
void _refreshCPURawData();
std::array<qulonglong, static_cast<short>(Labels::Label::SIZE)>
currentCPULoad;
};
#endif // SYSTEM_INFOWINDOWSIMPL_H
具体的实现,可以参考任何一本Windows编程类的书进行学习,这里只是给出实现供各位参考
#include "System_InfoWindowsImpl.h"
#include <Windows.h>
System_InfoWindowsImpl::System_InfoWindowsImpl() : System_InfoImpl() {
}
qulonglong System_InfoWindowsImpl::WindowsTools::fromWindowsFileTime(
const FILETIME& fileTime) {
ULARGE_INTEGER integer;
integer.LowPart = fileTime.dwLowDateTime;
integer.HighPart = fileTime.dwHighDateTime;
return integer.QuadPart;
}
void System_InfoWindowsImpl::_initWork() {
_refreshCPURawData();
}
void System_InfoWindowsImpl::_refreshCPURawData() {
FILETIME idle, kernel, user;
::GetSystemTimes(&idle, &kernel, &user);
currentCPULoad[Labels::to_index(Labels::Label::IDLE)] =
WindowsTools::fromWindowsFileTime(idle);
currentCPULoad[Labels::to_index(Labels::Label::KERN)] =
WindowsTools::fromWindowsFileTime(kernel);
currentCPULoad[Labels::to_index(Labels::Label::USER)] =
WindowsTools::fromWindowsFileTime(user);
}
double System_InfoWindowsImpl::_cpuLoadAverage() {
std::array<qulonglong, static_cast<short>(Labels::Label::SIZE)> previous =
currentCPULoad;
_refreshCPURawData();
#define FAST_CALC(var_name, LABEL_NAME) \
qulonglong var_name = \
currentCPULoad[Labels::to_index(Labels::Label::LABEL_NAME)] - \
previous[Labels::to_index(Labels::Label::LABEL_NAME)]
FAST_CALC(cur_idle, IDLE);
FAST_CALC(cur_kern, KERN);
FAST_CALC(cur_user, USER);
#undef FAST_CALC
qulonglong systems = cur_kern + cur_user;
double percentage = (systems - cur_idle) * 100.0 / (double)systems;
return qBound(0.0, percentage, 100.0);
}
double System_InfoWindowsImpl::_memoryUsed() {
MEMORYSTATUSEX mem;
mem.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&mem);
return (mem.ullTotalPhys - mem.ullAvailPhys) * 100.0 / mem.ullTotalPhys;
}
但是这并并有结束,我们还差了IMPL与INTERFACE的部分的鸿沟,为此,我们需要做的是,将类的实现类移动到一个工厂类来负责实现,工厂来裁决生成如何的实现类去!
#ifndef SYSTEM_INFOIMPL_FACTORY_H
#define SYSTEM_INFOIMPL_FACTORY_H
#include "System_InfoImpl.h"
class System_InfoImpl_Factory {
public:
static System_InfoImpl* createImplementInstance();
};
#endif // SYSTEM_INFOIMPL_FACTORY_H
#include "System_InfoImpl_Factory.h"
#include <QtGlobal>
#ifdef Q_OS_WIN
#include "System_InfoWindowsImpl.h"
static System_InfoWindowsImpl* instances() {
return new System_InfoWindowsImpl;
}
#elif defined(Q_OS_LINUX)
// Waiting Implements
#endif
System_InfoImpl* System_InfoImpl_Factory::createImplementInstance() {
return instances();
}
现在,我们就可以使用工厂和抽象的实现类,完成我们对系统信息前端类的实现了:
#ifndef SYSTEM_INFO_H
#define SYSTEM_INFO_H
#include <QtClassHelperMacros> // Q_DISABLE_COPY
class System_InfoImpl;
class System_Info {
public:
System_Info();
Q_DISABLE_COPY(System_Info);
~System_Info();
// Functions
double cpuLoadAverage();
double memoryUsed();
private:
void createAccordingPlatform();
System_InfoImpl* impl;
};
#endif // SYSTEM_INFO_H
#include "System_Info.h"
#include "System_InfoImpl.h"
#include "System_InfoImpl_Factory.h"
System_Info::System_Info() {
impl = System_InfoImpl_Factory::createImplementInstance();
}
double System_Info::cpuLoadAverage() {
return impl->_cpuLoadAverage();
}
double System_Info::memoryUsed() {
return impl->_memoryUsed();
}
System_Info::~System_Info() {
delete impl;
}
看起来相当的简洁!
我们下面就可以使用这个接口了:
08:50:24: Starting D:\QT projects\SystemInfoChecker\build\Desktop_Qt_6_6_1_MSVC2019_64bit-Debug\debug\SystemInfoChecker.exe... 50 36.7122
当然Linux的实现如法炮制的,这里放一下源码:
-
首先在pro文件修改成如下的代码:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
System_Info.cpp \
System_InfoImpl.cpp \
System_InfoImpl_Factory.cpp \
main.cpp \
systeminfowindow.cpp
HEADERS += \
System_Info.h \
System_InfoImpl.h \
System_InfoImpl_Factory.h \
systeminfowindow.h
FORMS += \
systeminfowindow.ui
# 这里体现了跨平台的地方
# 对于Windows,向工具链提供这些文件
windows {
HEADERS += System_InfoWindowsImpl.h \
SOURCES += System_InfoWindowsImpl.cpp \
}
# 对Linux是另一些
linux {
HEADERS += system_infolinuximpl.h \
SOURCES += system_infolinuximpl.cpp \
}
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
-
写一个实现文件对
h文件
#ifndef SYSTEM_INFOLINUXIMPL_H
#define SYSTEM_INFOLINUXIMPL_H
#include "System_InfoImpl.h"
#include <QByteArray>
class System_InfoLinuxImpl : public System_InfoImpl
{
public:
System_InfoLinuxImpl();
Q_DISABLE_COPY(System_InfoLinuxImpl)
virtual ~System_InfoLinuxImpl() = default;
// Impls
void _initWork() override;
double _cpuLoadAverage() override;
double _memoryUsed() override;
private:
struct LinuxTools{
static QByteArray fromLinuxStatFile();
};
struct Labels {
enum class Label { IDLE = 0, KERN = 1, USER = 2, USER_NICE = 3, SIZE };
static constexpr short to_index(Label l) {
return static_cast<short>(l);
}
};
void _refreshCPURawData();
std::array<qulonglong, static_cast<short>(Labels::Label::SIZE)>
currentCPULoad;
};
#endif // SYSTEM_INFOLINUXIMPL_H
CPP文件
#include "system_infolinuximpl.h"
#include <QFile>
#include <sys/types.h>
#include <sys/sysinfo.h>
QByteArray System_InfoLinuxImpl::LinuxTools::fromLinuxStatFile(){
QFile file("/proc/stat");
file.open(QIODevice::ReadOnly);
QByteArray dataLine = file.readLine();
file.close();
return dataLine;
}
System_InfoLinuxImpl::System_InfoLinuxImpl() : System_InfoImpl(){
}
void System_InfoLinuxImpl::_initWork(){
_refreshCPURawData();
}
void System_InfoLinuxImpl::_refreshCPURawData(){
QByteArray fromFile = LinuxTools::fromLinuxStatFile();
qulonglong tol_user = 0, userNice = 0, sys = 0, idle = 0;
std::sscanf(fromFile.data(), "cpu %llu %llu %llu %llu", &tol_user, &userNice, &sys, &idle);
#define FAST_REG(LABEL_NAME, var) \
currentCPULoad[Labels::to_index(Labels::Label::LABEL_NAME)] = var
FAST_REG(IDLE, idle);
FAST_REG(USER, tol_user);
FAST_REG(USER_NICE, userNice);
FAST_REG(KERN, sys);
#undef FAST_REG
}
double System_InfoLinuxImpl::_cpuLoadAverage()
{
std::array<qulonglong, static_cast<short>(Labels::Label::SIZE)> previous =
currentCPULoad;
_refreshCPURawData();
#define GET_TIME(LABEL_NAME) \
currentCPULoad[Labels::to_index(Labels::Label::LABEL_NAME)] - \
previous[Labels::to_index(Labels::Label::LABEL_NAME)]
double overall = GET_TIME(USER) + GET_TIME(KERN) + GET_TIME(USER_NICE);
double tol = overall + GET_TIME(IDLE);
double per = overall * 100.0 / tol;
return qBound(0.0, per, 100.0);
}
double System_InfoLinuxImpl::_memoryUsed()
{
struct sysinfo meminfo;
sysinfo(&meminfo);
qulonglong tolMem = meminfo.totalram;
tolMem += meminfo.totalswap;
tolMem *= meminfo.mem_unit;
qulonglong used = meminfo.totalram - meminfo.freeram;
used += meminfo.totalswap - meminfo.freeswap;
used *= meminfo.mem_unit;
double per = used * 100.0 / tolMem;
return qBound(0.0, per, 100.0);
}
-
向工厂提供初始化一个IMPL类的实例方法:
#elif defined(Q_OS_LINUX)
#include "system_infolinuximpl.h"
static System_InfoLinuxImpl* instances() {
return new System_InfoLinuxImpl;
}
现在我们的代码可以无缝的游离在两个平台之间而无需改动代码,就可以编译运行了。















![[产品管理-10]:NPDP新产品开发 - 8 - 波士顿矩阵(当下与未来)在产品市场战略方面的应用](https://i-blog.csdnimg.cn/direct/4a8f881d966246439a6b84e6be416266.png)



