End to End GUI Development with Qt5
上QQ阅读APP看书,第一时间看更新

Adding the Windows implementation

Remember the UML diagram at the beginning of this chapter? The SysInfoWindowsImpl class is one of the classes derived from the SysInfo class. The main purpose of this class is to encapsulate the Windows-specific code to retrieve CPU and memory usage.

It's time to create the SysInfoWindowsImpl class. To do that, you need to perform the following steps:

  1. Right click on the ch02-sysinfo project name in the hierarchy view.
  2. Click on Add NewC++ ClassChoose.
  3. Set the Class name field to SysInfoWindowsImpl.
  4. Set the Base class field to <Custom> and write under the SysInfo class.
  5. Click on Next then Finish to generate an empty C++ class.

These generated files are a good starting point, but we must tune them:

#include "SysInfo.h" 
 
class SysInfoWindowsImpl : public SysInfo 
{ 
public: 
    SysInfoWindowsImpl(); 
 
    void init() override; 
    double cpuLoadAverage() override; 
    double memoryUsed() override; 
}; 

The first thing to do is to add the include directive to our parent class, SysInfo. You can now override virtual functions defined in the base class.

Qt Tip Put your cursor on a derived class name (after the keyword class) and press  AltEnter (Windows / Linux) or  CommandEnter (Mac) to automatically insert virtual functions of the base class.

The override keyword comes from C++11. It ensures that the function is declared as virtual in the base class. If the function signature marked as override does not match any parent class' virtual function, a compile-time error will be displayed.

Retrieving the current memory used on Windows is easy. We will begin with this feature in the SysInfoWindowsImpl.cpp file:

#include "SysInfoWindowsImpl.h" 
 
#include <windows.h> 
 
SysInfoWindowsImpl::SysInfoWindowsImpl() : 
    SysInfo(), 
{ 
} 
 
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; 
} 

Don't forget to include the windows.h file so that we can use the Windows API! Actually, this function retrieves the total and the available physical memory. A simple subtraction gives us the amount of memory used. As required by the base class SysInfo, this implementation will return the value as a double type; for example, the value 23.0 for 23% memory used on a Windows OS.

Retrieving the total memory used is a good start, but we cannot stop now. Our class must also retrieve the CPU load. The Windows API can be messy sometimes. To make our code more readable, we will create two private helper functions. Update your SysInfoWindowsImpl.h file to match the following snippet:

#include <QtGlobal> 
#include <QVector> 
 
#include "SysInfo.h" 
 
typedef struct _FILETIME FILETIME; 
 
class SysInfoWindowsImpl : public SysInfo 
{ 
public: 
    SysInfoWindowsImpl(); 
 
    void init() override; 
    double cpuLoadAverage() override; 
    double memoryUsed() override; 
 
private: 
    QVector<qulonglong> cpuRawData(); 
    qulonglong convertFileTime(const FILETIME& filetime) const; 
 
private: 
    QVector<qulonglong> mCpuLoadLastValues; 
}; 

Let's analyze these changes:

  • The cpuRawData() is the function that will perform the Windows API call to retrieve system timing information and return values in a generic format. We will retrieve and return three values: the amount of time that the system has spent in idle, in Kernel, and in User mode.
  • The convertFileTime() function is our second helper. It will convert a Windows FILETIME struct syntax to a qulonglong type. The qulonglong type is a Qt unsigned long long int. This type is guaranteed by Qt to be 64-bit on all platforms. You can also use the typedef quint64.
  • The mCpuLoadLastValues is a variable that will store system timing (idle, Kernel, and User) at a given moment.
  • Don't forget to include the <QtGlobal> tag to use the qulonglong type, and the <QVector> tag to use the QVector class.
  • The syntax typedef struct _FILETIME FILETIME is a kind of forward declaration for FILENAME syntax. As we only use a reference, we can avoid including the <windows.h> tag in our file SysInfoWindowsImpl.h and keep it in the CPP file.

We can now switch to the file SysInfoWindowsImpl.cpp and implement these functions to finish the CPU load average feature on Windows:

#include "SysInfoWindowsImpl.h" 
 
#include <windows.h> 
 
SysInfoWindowsImpl::SysInfoWindowsImpl() : 
    SysInfo(), 
    mCpuLoadLastValues() 
{ 
} 
 
void SysInfoWindowsImpl::init() 
{ 
    mCpuLoadLastValues = cpuRawData(); 
} 

When the init() function is called, we store the return value from the cpuRawData() function in our class variable mCpuLoadLastValues. It will be helpful for the cpuLoadAverage()function process.

You may be wondering why we do not perform this task in the initialization list of the constructor. That is because when you call a function from the initialization list, the object is not yet fully constructed! In some circumstances, it may be unsafe because the function can try to access a member variable that has not been constructed yet. However, in this ch02-sysinfo project, the cpuRawData function does not use any member variables, so you are safe, if you really want to do it. Add the cpuRawData() function to the SysInfoWindowsImpl.cpp file:

QVector<qulonglong> SysInfoWindowsImpl::cpuRawData() 
{ 
    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; 
} 

Here we are: the Windows API call to the GetSystemTimes function! This function will give us the amount of time that the system has spent idle, and in the Kernel and User modes. Before filling the QVector class, we convert each value with our helper convertFileTime described in the following code:

qulonglong SysInfoWindowsImpl::convertFileTime(const FILETIME& filetime) const 
{ 
    ULARGE_INTEGER largeInteger; 
    largeInteger.LowPart = filetime.dwLowDateTime; 
    largeInteger.HighPart = filetime.dwHighDateTime; 
    return largeInteger.QuadPart; 
} 

The Windows structure FILEFTIME stores 64-bit information on two 32-bit parts (low and high). Our function convertFileTime uses the Windows structure ULARGE_INTEGER to correctly build a 64-bit value in a single part before returning it as a qulonglong type. Last but not least, the cpuLoadAverage() implementation:

double SysInfoWindowsImpl::cpuLoadAverage() 
{ 
    QVector<qulonglong> firstSample = mCpuLoadLastValues; 
    QVector<qulonglong> secondSample = cpuRawData(); 
    mCpuLoadLastValues = secondSample; 
 
    qulonglong currentIdle = secondSample[0] - firstSample[0]; 
    qulonglong currentKernel = secondSample[1] - firstSample[1]; 
    qulonglong currentUser = secondSample[2] - firstSample[2]; 
    qulonglong currentSystem = currentKernel + currentUser; 
 
    double percent = (currentSystem - currentIdle) * 100.0 / 
        currentSystem ; 
    return qBound(0.0, percent, 100.0); 
} 

There are three important points to note here:

  • Keep in mind that a sample is an absolute amount of time, so subtracting two different samples will give us instantaneous values that can be processed to get the current CPU load.
  • The first sample comes from our member variable mCpuLoadLastValues, probed the first time by the init() function. The second one is retrieved when the cpuLoadAverage() function is called. After initializing the samples, the mCpuLoadLastValues variable can store the new sample that will be used for the next call.
  • The percent equation can be a little tricky because the Kernel value retrieved from the Windows API also contains the idle value.
If you want to learn more about the Windows API, take a look at the MSDN documentation at https://msdn.microsoft.com/library.

The final step to finish the Windows implementation is to edit the file ch02-sysinfo.pro so that it resembles the following snippet:

QT       += core gui 
CONFIG   += C++14 
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 
 
TARGET = ch02-sysinfo 
TEMPLATE = app 
 
SOURCES += main.cpp  
    MainWindow.cpp  
    SysInfo.cpp 
 
HEADERS += MainWindow.h  
    SysInfo.h 
 
windows { 
    SOURCES += SysInfoWindowsImpl.cpp 
    HEADERS += SysInfoWindowsImpl.h 
} 
 
FORMS    += MainWindow.ui 

As we did in the ch01-todo project, we also use C++14 with the ch02-sysinfo project. The really new point here is that we removed the files SysInfoWindowsImpl.cpp and SysInfoWindowsImpl.h from the common SOURCES and HEADERS variables. Indeed, we added them into a windows platform scope. When building for other platforms, those files will not be processed by qmake. That is why we can safely include a specific header such as windows.h in the source file SysInfoWindowsImpl.cpp without harming the compilation on other platforms.