Qt学习之旅 I

news/2024/12/22 1:47:18/

构建一个跨平台的应用(Create A Cross-Platform Application)

目录

构建一个跨平台的应用(Create A Cross-Platform Application)

设计模式

开始构建


Qt是跨平台的C++框架,这里,我们将会构建一个简单的C++跨平台项目来熟悉QT是如何实现简单的跨平台的。

我们将要构建的是一个动态查看,不同操作系统下操作系统运行时的一些状态,这就是一个跨平台的需求。

设计模式

要从 OS(操作系统)检索 CPU 和内存使用情况,我们将使用一些特定于平台的代码。 为了成功完成此任务,我们将使用两种设计模式:

  1. 策略模式:这是一个描述功能的接口(例如,检索 CPU 使用情况),特定行为(在 Windows/macOS/Linux 上检索 CPU 使用情况)将在实现此接口的子类中执行。

  2. pIMPL法:pointer to implements方法

  3. 单例模式:此模式保证给定类只有一个实例。此实例将通过唯一访问点轻松访问。

  4. 工厂模式:返回对应子类的实例

我们将会使用一个单例:System_Info,这是可以理解的因为我们的应用软件只会跑在一个操作系统上,因此没有必要创建多个实例,这种方式广泛的应用在那些维护全局状态的对象当中,也是一个常见的设计模式了。

开始构建

我们刚刚大致的想法就是如此,现在我们开始构建代码。我们仔细思考,作为用户,他只会关心接口的功能而不在乎里面的实现。为此,我们需要做的是使用pIMPL法,把设计到具体的项目平台的代码给他封装起来,这样可以减轻我们的管理负担。

因此,我们先带有尝试性质的——设计这些必要的接口:

#ifndef SYSTEM_INFOIMPL_H
#define SYSTEM_INFOIMPL_H
​
#include <QtClassHelperMacros>
​
/*The Actual Action classAll Bases For Every Possible OperatingSystem
*/
class System_InfoImpl {
public:System_InfoImpl() = default;Q_DISABLE_COPY(System_InfoImpl);virtual ~System_InfoImpl() = default;// Impl Interfacesvirtual 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;
​// Implsvoid   _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_CALCqulonglong 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();// Functionsdouble 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的实现如法炮制的,这里放一下源码:

  1. 首先在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

  1. 写一个实现文件对

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;// Implsvoid   _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);
}
  1. 向工厂提供初始化一个IMPL类的实例方法:

#elif defined(Q_OS_LINUX)
#include "system_infolinuximpl.h"
static System_InfoLinuxImpl* instances() {return new System_InfoLinuxImpl;
}

现在我们的代码可以无缝的游离在两个平台之间而无需改动代码,就可以编译运行了。


http://www.ppmy.cn/news/1527359.html

相关文章

JUnit 5 详解

JUnit 5 详解 JUnit 是 Java 生态系统中最流行的测试框架之一&#xff0c;用于编写单元测试、集成测试等。JUnit 5 是其最新版本&#xff0c;提供了更多功能和灵活性&#xff0c;旨在提高测试的可读性、可维护性和可扩展性。JUnit 5 在设计上有别于之前的版本&#xff0c;并引…

【Kubernetes】常见面试题汇总(十一)

目录 33.简述 Kubernetes 外部如何访问集群内的服务&#xff1f; 34.简述 Kubernetes ingress &#xff1f; 35.简述 Kubernetes 镜像的下载策略&#xff1f; 33.简述 Kubernetes 外部如何访问集群内的服务&#xff1f; &#xff08;1&#xff09;对于 Kubernetes&#xff0…

计算机毕业设计选题推荐-养老院管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Clickhouse如何完全保证数据的去重

在ClickHouse中&#xff0c;实现数据去重是为了避免在大规模分布式环境中数据的重复存储和计算&#xff0c;这对于保持数据的一致性和准确性非常重要。ClickHouse可以通过多种机制确保数据的去重&#xff0c;从数据表结构、插入去重、数据合并去重、查询去重等多个方面入手&…

el-table使用合计和固定列时,滚动条被覆盖区域无法拖拽问题

pointer-events文档 解决思路为通过pointer-events实现事件穿透&#xff0c;不响应固定列的拖拽&#xff0c;而是响应其子元素的拖拽事件 /deep/.el-table__fixed, /deep/.el-table__fixed-right {pointer-events: none; } /deep/.el-table__fixed *, /deep/.el-table__fixed-…

ClickHouse--19-- 分布式 GLOBAL IN 和 GLOBAL JOIN

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、前言二、案例分析单机场景&#xff1a;分布式场景&#xff1a;第一种改法第二种改法第三种改法查询放大怎么解决呢? ClickHouse为我们提供了解决方案&#xff…

3.Java高级编程实用类介绍(一)

三、Java高级编程实用类介绍(一) 文章目录 三、Java高级编程实用类介绍(一)一、枚举类型二、包装类三、Math 一、枚举类型 使用enum进行定义 public enum 枚举名字{值1,值2.... }二、包装类 每个基本类型在java.lang包中都有一个相应的包装类 /** new包装类&#xff08;字符…

基于 Delphi 的家庭财务管理系统

基于 Delphi 的家庭财务管理系统可以帮助用户跟踪家庭的收支情况&#xff0c;包括日常开销、收入、储蓄等信息。这样的系统通常包括账户管理、交易记录、预算规划和财务报告等功能。下面是一个简化版的家庭财务管理系统的设计方案及其代码示例。 系统设计概览 账户管理&#…