尽管 Qt Quick C++ 项目中的 qDebug 输出正确,但未定义值错误和 QML 文本字段未更新

问题描述 投票:0回答:1

下面是我编写的一个函数,旨在使用 COM 和 WMI 返回用户 PC 存储的名称:

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;
}

在 App.qml 中,我将值分配给文本字段:

 Component.onCompleted: {
        mainScreen.gpuInfoField.text = sysInfo.getGpuInfo();
        mainScreen.diskInfoField.text = sysInfo.getDiskInfo();
    }

构建项目时,由

qDebug()
触发的控制台输出显示预期值,即磁盘名称。但随后它会抛出此错误,并且文本字段的值不会更改:

qrc:/qt/qml/content/App.qml:20: TypeError: Value is undefined and could not be converted to an object

类似的写法getGpuInfo();功能正常。

要重现此问题,请在 Qt Creator 中创建一个 Qt Quick 项目,并添加一个带有 getDiskInfo() 函数的类“systeminfo.cpp”,不要忘记初始化 COM 并包含 Wbemidl.h 和 comdef.h。在 Qt Design Studio 中添加一个文本字段并尝试在 App.qml 中分配值。这是显而易见的,但请注意,Windows 以外的任何操作系统都无法工作,因为 Windows Management Instrumentation API 是特定于 Windows 的。

我花了一整天的时间试图弄清楚到底出了什么问题,添加了很多错误处理程序和 qDebugs,然后完全删除了该函数并编写了一个类似的函数 getRamInfo(),具有完全相同的问题和完全相同的问题同样的错误。 qDebug() 将预期值输出到控制台,但仍然出现错误,指出该值未定义。 getGpuInfo() 功能正常,并且实际上几乎相同。我最初认为这个错误是 getDiskInfo() 特有的,但新编写的函数中的相同问题暗示了相反的情况。不知道出了什么问题,因为该函数本身似乎工作正常并打印磁盘名称。如果您需要更多信息来尝试找到该问题的解决方案,请随时在评论中写下。我真的需要帮助。

此时任何帮助、任何想法、任何事情都将受到高度赞赏。

最小可重现示例 旁注:有趣的是,当我创建并构建这个示例时,所有功能都不起作用。甚至 getGpuInfo();函数不再按预期工作,虽然 qDebug() 在控制台中显示预期值,但存在类型错误。我肯定错过了一些东西。

在新创建的 Qt Quick 项目中创建一个

systeminfo
类。 .cpp 代码必须包含 COM 初始化函数 getGpuInfo() 和 getDiskInfo() ,如下所示:

#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;
}

在头文件(systeminfo.h)中定义systeminfo类

#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

在 main.cpp 文件中定义一个 sysInfo 对象并使其可用于 QML,包括 QQmlContext 和 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();
}

请注意,我的代码中的

#include ".../systeminfo.h"
假设该文件位于项目的根目录中。如果您在其他地方创建了 systeminfo 类,则显然需要指定该路径。

在 Screen01.ui.qml 中创建两个带有 id

gpuInfoField
diskInfoField
的文本区域并禁用它们:

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")
    }
}

将值分配给 App.qml 中的文本区域:

    Component.onCompleted: {
            mainScreen.gpuInfoField.text = sysInfo.getGpuInfo();
            mainScreen.diskInfoField.text = sysInfo.getDiskInfo();
        }

最后,确保在 CMakeLists.txt 中链接

wbemuuid
库:

target_link_libraries(MREApp PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Qml
    Qt6::Quick
    wbemuuid #Add this library
)

并构建您的项目。就我而言,文本区域的值不会更改,控制台输出如下所示:

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
c++ qt com qml wmi
1个回答
0
投票

您的问题与您正在读取的 C++ 对象无关。这是您尝试为其分配文本的 TextArea 对象的问题。您无法访问对象的子对象 ID。相反,您需要通过属性公开对象。在上面的评论中,我应该建议使用属性别名而不仅仅是属性,但它们都应该在这种情况下工作。

主屏幕.qml

Rectangle {
    // Expose objects that are accessed from outside
    property alias gpuInfoField: gpuInfoField
    property alias diskInfoField: diskInfoField

    TextArea {
        id: gpuInfoField
    }

    TextArea {
        id: diskInfoField
    }
}

应用程序.qml

Window {
    MainScreen {
        id: mainScreen
    }

    Component.onCompleted: {
        mainScreen.gpuInfoField.text = sysInfo.getGpuInfo();
        mainScreen.diskInfoField.text = sysInfo.getDiskInfo();
    }
}

话虽如此,您通常不需要访问整个对象。通常最好简单地公开类外所需的子级的属性:

主屏幕.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
    }
}

应用程序.qml

Window {
    MainScreen {
        id: mainScreen
    }

    Component.onCompleted: {
        mainScreen.gpuText = sysInfo.getGpuInfo();
        mainScreen.diskText = sysInfo.getDiskInfo();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.