Below is a function I wrote that's designed to return the name of the user's PC storage using COM and WMI:
Q_INVOKABLE QString systeminfo::getDiskInfo() {
// Disk info
HRESULT hres;
IWbemLocator* pLoc = NULL;
IWbemServices* pSvc = NULL;
initializeCOM(hres, pLoc, pSvc);
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
ConvertStringToBSTR("WQL"),
ConvertStringToBSTR("SELECT * FROM Win32_DiskDrive"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
std::cout << "Error code = 0x" << std::hex << hres << std::endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return QString(); // Returns an empty string in case of an error
}
// Getting data
QString storageModel;
while (pEnumerator)
{
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (0 == uReturn)
{
break;
}
VARIANT vtProp;
hr = pclsObj->Get(L"Model", 0, &vtProp, 0, 0);
storageModel = QString::fromWCharArray(vtProp.bstrVal);
qDebug() << "Storage: " << storageModel;
VariantClear(&vtProp);
pclsObj->Release();
}
// Cleanup
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();
return storageModel;
}
In App.qml, I assigned the value to a text field:
Component.onCompleted: {
mainScreen.gpuInfoField.text = sysInfo.getGpuInfo();
mainScreen.diskInfoField.text = sysInfo.getDiskInfo();
}
When building the project, a console output triggered by qDebug()
shows the expected value, the name of the disk. But then it throws this error and the value of the text field doesn't change:
qrc:/qt/qml/content/App.qml:20: TypeError: Value is undefined and could not be converted to an object
The similarly written getGpuInfo(); function works properly.
To reproduce this issue, create a Qt Quick project in Qt Creator and add a class "systeminfo.cpp" with getDiskInfo() function, don't forget to initialize COM and include Wbemidl.h and comdef.h. Add a text field in Qt Design Studio and try to assign the value in App.qml. It's obvious but note that any OS other than Windows wouldn't work because Windows Management Instrumentation API is, well, Windows-specific.
I was trying to figure out what on earth could be wrong for an entirety of a day, adding lots off error handlers and qDebugs and then erased that function completely and wrote a similar function getRamInfo(), with the exact same issue and the exact same error. The qDebug() outputs the expected value to the console, but then there's still an error stating that the value is undefined. The getGpuInfo() functions properly and it's literally nearly the same. I initially thought that this error was specific to the getDiskInfo(), but the same problem in the newly written function hinted at the opposite. No idea what is wrong, because the function itself seems to work fine and prints the name of the disk. If you need more info to try to find the fix to that issue, feel free to write in comments. I really need help.
Any help, any ideas, anything at this point will be highly appreciated.
Minimal Reproducible Example Side Note: Interestingly, when I created this example and built it, none of the functions worked. Even the getGpuInfo(); function no longer works as expected, and while qDebug() shows the expected value in the console, there's a TypeError. I am definitely missing something.
In a newly created Qt Quick project create a systeminfo
class. The .cpp code has to include the COM initialization function, getGpuInfo() and getDiskInfo() and look like this:
#define _WIN32_DCOM
#include <iostream>
#include <comdef.h>
#include <Wbemidl.h>
#include "systeminfo.h"
#include <QDebug>
#pragma comment(lib, "wbemuuid.lib")
systeminfo::systeminfo(QObject *parent) : QObject(parent) {
HRESULT hres;
IWbemLocator* pLoc = nullptr;
IWbemServices* pSvc = nullptr;
initializeCOM(hres, pLoc, pSvc);
}
// Function for converting char* to BSTR.
BSTR ConvertStringToBSTR(const char *pSrc)
{
const size_t cSize = strlen(pSrc)+1;
wchar_t* wc = new wchar_t[cSize];
mbstowcs (wc, pSrc, cSize);
BSTR bstr = SysAllocString(wc);
delete [] wc;
return bstr;
}
void systeminfo::initializeCOM(HRESULT& hres, IWbemLocator*& pLoc, IWbemServices*& pSvc) {
// Initializing COM.
hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hres) && hres != RPC_E_CHANGED_MODE)
{
std::cout << "There was an error initializing COM. Error code = 0x" << std::hex << hres << std::endl;
return;
}
// Configuring COM security level.
hres = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL);
if(hres == RPC_E_TOO_LATE)
hres = S_OK;
else if (FAILED(hres))
{
std::cout << "There was an error configuring COM security level. Error code = 0x" << std::hex << hres << std::endl;
CoUninitialize();
return;
}
// Getting a pointer to the WMI service.
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID*)&pLoc);
if (FAILED(hres))
{
std::cout << "Error creating IWbemLocator instance. Error code = 0x" << std::hex << hres << std::endl;
CoUninitialize();
return;
}
// Connecting to WMI namespace.
hres = pLoc->ConnectServer(
ConvertStringToBSTR("ROOT\\CIMV2"),
nullptr,
nullptr,
0,
0,
0,
0,
&pSvc);
if (FAILED(hres))
{
std::cout << "There was an error connecting to WMI namespace. Error code = 0x" << std::hex << hres << std::endl;
pLoc->Release();
CoUninitialize();
return;
}
// Setting the security level for WMI proxy.
hres = CoSetProxyBlanket(
pSvc,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE);
if (FAILED(hres))
{
std::cout << "Error setting security level. Error code = 0x" << std::hex << hres << std::endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return;
}
}
Q_INVOKABLE QString systeminfo::getGpuInfo() {
HRESULT hres;
IWbemLocator* pLoc = NULL;
IWbemServices* pSvc = NULL;
initializeCOM(hres, pLoc, pSvc);
// Using WMI to retrieve information about the video controller.
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
ConvertStringToBSTR("WQL"),
ConvertStringToBSTR("SELECT * FROM Win32_VideoController"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
std::cout << "Query execution failed. Error code = 0x" << std::hex << hres << std::endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return QString(); // Returns an empty string in case of an error.
}
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;
QString gpuInfo;
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (0 == uReturn)
{
break;
}
VARIANT vtProp;
// Getting the name of the GPU.
hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
gpuInfo = QString::fromWCharArray(vtProp.bstrVal);
qDebug() << "GPU: " << gpuInfo;
VariantClear(&vtProp);
pclsObj->Release();
}
// Cleanup.
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();
return gpuInfo;
}
Q_INVOKABLE QString systeminfo::getDiskInfo() {
// Using WMI to get retrieve information about the storage.
HRESULT hres;
IWbemLocator* pLoc = NULL;
IWbemServices* pSvc = NULL;
initializeCOM(hres, pLoc, pSvc);
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
ConvertStringToBSTR("WQL"),
ConvertStringToBSTR("SELECT * FROM Win32_DiskDrive"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
std::cout << "Query execution failed. Error code = 0x" << std::hex << hres << std::endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return QString(); // Returns an empty string in case of an error.
}
QString storageModel;
while (pEnumerator)
{
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (0 == uReturn)
{
break;
}
VARIANT vtProp;
// Getting the name of the disk.
hr = pclsObj->Get(L"Model", 0, &vtProp, 0, 0);
storageModel = QString::fromWCharArray(vtProp.bstrVal);
qDebug() << "Storage: " << storageModel;
VariantClear(&vtProp);
pclsObj->Release();
}
// Cleanup.
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();
return storageModel;
}
Define the systeminfo class in the header file (systeminfo.h)
#ifndef SYSTEMINFO_H
#define SYSTEMINFO_H
#include <QObject>
#include <comdef.h>
#include <Wbemidl.h>
interface IWbemLocator;
interface IWbemServices;
class systeminfo : public QObject
{
Q_OBJECT
public:
explicit systeminfo(QObject *parent = nullptr);
Q_INVOKABLE QString getGpuInfo();
Q_INVOKABLE QString getDiskInfo();
private:
void initializeCOM(HRESULT& hres, IWbemLocator*& pLoc, IWbemServices*& pSvc);
};
#endif // SYSTEMINFO_H
In your main.cpp file define a sysInfo object and make it available to QML, include QQmlContext and systeminfo.h:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext> // Add this line
#include "app_environment.h"
#include "import_qml_components_plugins.h"
#include "import_qml_plugins.h"
#include "../systeminfo.h" // And this
int main(int argc, char *argv[])
{
set_qt_environment();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// Add these two lines below:
systeminfo sysInfo;
engine.rootContext()->setContextProperty("sysInfo", &sysInfo);
const QUrl url(u"qrc:/qt/qml/Main/main.qml"_qs);
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreated,
&app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.addImportPath(QCoreApplication::applicationDirPath() + "/qml");
engine.addImportPath(":/");
engine.load(url);
if (engine.rootObjects().isEmpty()) {
return -1;
}
return app.exec();
}
Note that #include ".../systeminfo.h"
in my code assumes that the file is located in the root directory of the project. If you created a systeminfo class somewhere else, you need to specify that path - obviously.
In Screen01.ui.qml create two text areas with ids gpuInfoField
and diskInfoField
and make them disabled:
import QtQuick 6.2
import QtQuick.Controls 6.2
import nameofyourproject
Rectangle {
id: rectangle
width: Constants.width
height: Constants.height
color: Constants.backgroundColor
TextArea {
id: gpuInfoField
x: 511
y: 267
width: 714
height: 136
enabled: false
placeholderText: qsTr("Text Area")
}
TextArea {
id: diskInfoField
x: 511
y: 453
width: 714
height: 136
enabled: false
placeholderText: qsTr("Text Area")
}
}
Assign the values to the text areas in App.qml:
Component.onCompleted: {
mainScreen.gpuInfoField.text = sysInfo.getGpuInfo();
mainScreen.diskInfoField.text = sysInfo.getDiskInfo();
}
Finally, make sure to link wbemuuid
library in CMakeLists.txt:
target_link_libraries(MREApp PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
wbemuuid #Add this library
)
And build your project. In my case, text areas' values aren't changed and the console output looks like this:
GPU: "NVIDIA GeForce RTX 3080 Ti"
qrc:/qt/qml/content/App.qml:19: TypeError: Value is undefined and could not be converted to an object
Your problem had nothing to do with your C++ object that you were reading from. It was a problem with the TextArea objects that you were trying to assign text to. You cannot access an object's children's IDs. Instead you need to expose the objects through properties. In my comment above, I should have suggested using a property alias instead of just a property, but they both should work in this case.
MainScreen.qml
Rectangle {
// Expose objects that are accessed from outside
property alias gpuInfoField: gpuInfoField
property alias diskInfoField: diskInfoField
TextArea {
id: gpuInfoField
}
TextArea {
id: diskInfoField
}
}
App.qml
Window {
MainScreen {
id: mainScreen
}
Component.onCompleted: {
mainScreen.gpuInfoField.text = sysInfo.getGpuInfo();
mainScreen.diskInfoField.text = sysInfo.getDiskInfo();
}
}
That being said, you usually don't need access to the entire object. It's often better to simply expose the properties of the children that are needed outside the class:
MainScreen.qml
Rectangle {
// Just expose the text properties, not the whole object
property alias gpuText: gpuInfoField.text
property alias diskText: diskInfoField.text
TextArea {
id: gpuInfoField
}
TextArea {
id: diskInfoField
}
}
App.qml
Window {
MainScreen {
id: mainScreen
}
Component.onCompleted: {
mainScreen.gpuText = sysInfo.getGpuInfo();
mainScreen.diskText = sysInfo.getDiskInfo();
}
}