构建一个跨平台的应用(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 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的实现如法炮制的,这里放一下源码:
-
首先在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;// 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); }
-
向工厂提供初始化一个IMPL类的实例方法:
#elif defined(Q_OS_LINUX) #include "system_infolinuximpl.h" static System_InfoLinuxImpl* instances() {return new System_InfoLinuxImpl; }
现在我们的代码可以无缝的游离在两个平台之间而无需改动代码,就可以编译运行了。