【问题标题】:Qt app using QNetworkAccessManager crashes when built for WebAssembly为 WebAssembly 构建时使用 QNetworkAccessManager 的 Qt 应用程序崩溃
【发布时间】:2021-08-07 09:42:39
【问题描述】:

我正在尝试制作通过 REST api 从服务器下载信息的简单应用程序。对于下载数据,我使用 QNetworkAccessManager。我的应用程序在为桌面构建时运行良好,我正在尝试为 WebAssembly 构建它。

我正在使用:

  • Qt 版本 5.15.2
  • Emscripten 版本 1.39.8(由 this 网站编写,是特定 Qt 版本的已知良好版本)

我已经使用-feature-thread 从源代码构建了 Qt webassembly,以支持多线程,并且内存/工作者(线程)设置是

 QMAKE_WASM_PTHREAD_POOL_SIZE = 8
 QMAKE_WASM_TOTAL_MEMORY = 3GB

当我将它用作本机桌面时,一切正常。当我通过浏览器(WebAssembly)启动它时,应用程序启动,GUI 出现,我可以输入连接地址。当我编写一个有效的(支持 CORS 的服务器)时,请求没有问题并返回有效的 JSON 文档。但是当我输入无效地址时,应用程序崩溃

崩溃是由 Qt (call stack) 中某处的 abort() 函数引起的。此外,应用程序的行为不一致 - 有时会在第一次请求时发生崩溃,有时在第三次请求时发生。

我遇到了两个可能的错误 - Uncaught RuntimeError: abort(undefined)Uncaught RuntimeError: abort(Runtime error: The application has corrupted its heap memory area (address zero)!)

我为 QNetworkAccessManager 尝试了多个教程,但结果相同。

ma​​in.cpp

#include "DummyRequestManager.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    DummyRequestManager c;
    c.init();

    return app.exec();
}

DummyRequestManager.hpp

#ifndef DUMMYREQUESTMANAGER_H
#define DUMMYREQUESTMANAGER_H

#include <QQuickView>
#include <QObject>
#include <QNetworkAccessManager>
#include <QJsonDocument>
#include <memory>

enum HttpRespStatus
{
    NoResponse = 0,
    Ok = 200,
    NoContent = 204,
    BadRequest = 400,
    Unauthorized = 401,
    Forbidden = 403,
    NotFound = 404,
    MethodNotAllowed = 405,
    NotAcceptable = 406, // Shouldn't be used.
    Conflict = 409,
    Gone = 410,
    UnsupportedMediaType = 415,
    InternalError = 500,
    NotImplemented = 501,
    ServiceUnavailable = 503
};


class DummyRequestManager : public QNetworkAccessManager
{
    Q_OBJECT
    Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
public:
    DummyRequestManager();

    void init();

    QString url() const { return url_; }

public slots:
    QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
    QNetworkReply* get(QNetworkRequest &request);

    void sendRequest();

public slots:
    // Setters
    void setUrl(QString url);

signals:
    void urlChanged(QString url);

private:
    int port_{0};
    QString scheme_{"https"};
    QString url_{""};
    QString finalUrl_{""};

    QQuickView view_;
};

#endif // DUMMYREQUESTMANAGER_H

DummyRequestManager.cpp

#include "DummyRequestManager.h"
#include <QQmlContext>
#include <QNetworkReply>
#include <QJsonObject>

DummyRequestManager::DummyRequestManager()
{
    connect(this, &DummyRequestManager::urlChanged, this, [=](){
        QStringList split = url_.split(":");
        QString addr = "";
        if(split.size() == 2)
        {
            addr = split.first();
            port_ = split.last().toInt();
            finalUrl_ = QString("https://%1:%2/api").arg(addr).arg(QString::number(port_));
            qDebug() << finalUrl_;
        }
    });
}

void DummyRequestManager::init()
{
    port_ = 8080;

    // WARNING Code below is supposed to be only for testing, it's necessary to add certificate
#ifndef QT_NO_SSL
    QSslConfiguration sslConf = QSslConfiguration::defaultConfiguration();
    sslConf.setPeerVerifyMode(QSslSocket::VerifyNone);
    QSslConfiguration::setDefaultConfiguration(sslConf);
#endif

    scheme_ = "https";

    view_.rootContext()->setContextProperty("dummy", this);

    view_.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
    view_.show();
}

QNetworkReply *DummyRequestManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
{
    return QNetworkAccessManager::createRequest(op, request, outgoingData);
}

QNetworkReply *DummyRequestManager::get(QNetworkRequest &request)
{
    qDebug() << "request url" << request.url();
    request.setRawHeader("accept", "application/json");

    request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
    return QNetworkAccessManager::get(request);
}

void DummyRequestManager::sendRequest()
{

    QUrl url(finalUrl_ + QString("/info"));
    url.setScheme(scheme_);
    QNetworkRequest request(url);

    QNetworkReply *reply = get(request);
    QObject::connect(reply, &QNetworkReply::finished, [=] {
        qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toString();
        qDebug() << reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toString();;
        qDebug() << reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
        qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();

        QByteArray data = reply->readAll();
        QJsonDocument doc = QJsonDocument::fromJson(data);
        qDebug().noquote() << doc.toJson(QJsonDocument::Indented);

        reply->deleteLater();
    });

}

void DummyRequestManager::setUrl(QString url)
{
    if (url_ == url)
        return;

    url_ = url;
    emit urlChanged(url_);
}

ma​​in.qml

import QtQuick 2.12

Item {
    width: 640
    height: 480
    visible: true

    Rectangle {
        id: centerRect
        width: 200
        height: 200
        anchors.centerIn: parent
        color: "blue"

        MouseArea {
            anchors.fill: parent
            onClicked: {
                dummy.sendRequest()
            }
        }
        Text {
            anchors.centerIn: parent
            text: qsTr("Send")
            color: "white"
        }
    }

    Rectangle {
        color: "gray"
        width: centerRect.width
        height: 50
        anchors.top: centerRect.top
        anchors.horizontalCenter: centerRect.horizontalCenter
        TextInput {
            anchors.fill: parent
            onTextChanged: {
                dummy.url = text
            }
        }
    }
}

你知道问题出在哪里吗?

感谢您的帮助!

编辑: 当我在 lambda 函数中删除 reply-&gt;deleteLater() 时它不会崩溃。

【问题讨论】:

    标签: c++ qt webassembly qnetworkaccessmanager


    【解决方案1】:

    一般来说,WebAssembly 对 QNetworkAccessManager 的支持是有限的,因为浏览器负责实际的 HTTP 请求,所以不支持 SSL 处理,例如你不能设置任何客户端证书。在 Qt(至少 5.14)中,多线程支持确实是有限的和错误的,但你应该可以使用单线程构建。 QML 应用程序通常在单线程构建的 WebAssembly 中运行良好,我知道因为我是 Felgo 努力将生产应用程序引入 wasm 的一部分,您可以在我们的网络编辑器 https://felgo.com/web-editor 中测试我们的结果(所有单线程)或我们的样品部分http://felgo.com/try-wasm

    【讨论】:

    • 我只是用单线程构建了它,并且有同样的问题,它看起来更像是 Qt 或 emscripten 中的错误
    • @Brykyz 你还在使用这个标志 QMAKE_WASM_TOTAL_MEMORY = 3GB 吗?最大为 1GB
    • 都试过了,同样的问题
    • @Brykyz 使用调试标志构建,它应该为您提供源映射,然后在浏览器开发者控制台上启用异常暂停,您应该会看到应用程序中导致崩溃的代码行
    • 它在 Qt/emscripten 包装器中崩溃。我报告的错误是here,看起来他们没有将修复程序合并到 Qt 中。
    猜你喜欢
    • 2015-12-31
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多