c++qtcomqmlwmi

Undefined value error and QML Text Field not updating despite correct qDebug output in Qt Quick C++ project


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

Solution

  • 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();
        }
    }