qtqt5qtnetwork

QT Callback with result after download is completed


I have a QT application where several different subcomponents need to fetch data from the internet. I want to have one Network class that will do all the network interaction and then notify the caller in some way. I couldn't figure out how to do this with slots, so I coded a callback solution:

void  Network::downloadFile(QString& urlstr, const std::function<void (QString, QByteArray, QNetworkReply::NetworkError)>& callback) {
    auto processDownload = [&]() {
        QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender());
        QByteArray result;
        QString filename = reply->url().fileName();
        if (reply->error() == QNetworkReply::NoError) { // Success
            result = reply->readAll();
         }
        callback(filename, result, reply->error());
        reply->deleteLater();
    };

    QNetworkReply *reply = nam_->get(QNetworkRequest(QUrl(urlstr)));
    connect(reply, &QNetworkReply::downloadProgress, this, &Network::downloadProgress);
    connect(reply, &QNetworkReply::finished, this, processDownload);
}

However I get a segfault as soon as callback is called. I tried several different ways to define the callback, and even having it empty, it still crashes (segfault mostly, sometimes bad function call). It is defined as:

std::function<void(QJsonObject, QNetworkReply::NetworkError)> f = std::bind(&MainWindow::onResult, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);

I resorted to doing something like static_cast<parent_class*>(this->parent())->onResult(..) but this is not really good decoupling. How can I do this in a way that I can pass (or signal to) any generic std::function?

EDIT: Downloadfile is usually called like this:

void MainWindow::DoNetworkRQ(QString url) {
    std::function<void(QJsonObject, QNetworkReply::NetworkError)> f = std::bind(&MainWindow::onResult, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    network_.downloadFile(url, f);
}

Which results in a segfault. How do I do this in a QT way?


Solution

  • @chehrlic was right that you are creating a callback function that goes out of scope before it gets called. Your question does not need to be solved in a "Qt way". But signals and slots will work, so I will show you how.

    First, connect your signal to your slot:

    void MainWindow::DoNetworkRQ(QString url) {
        connect(&network_, &Network::someSignal, this, &MainWindow::onResult);
        network_.downloadFile(url, f);
    }
    

    Then, emit the signal when your network tasks are completed:

    void  Network::downloadFile(QString& urlstr, const std::function<void (QString, QByteArray, QNetworkReply::NetworkError)>& callback) {
        auto processDownload = [&]() {
            QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender());
            QByteArray result;
            QString filename = reply->url().fileName();
            if (reply->error() == QNetworkReply::NoError) { // Success
                result = reply->readAll();
             }
            emit someSignal(filename, result, reply->error());
            reply->deleteLater();
        };
    
        QNetworkReply *reply = nam_->get(QNetworkRequest(QUrl(urlstr)));
        connect(reply, &QNetworkReply::downloadProgress, this, &Network::downloadProgress);
        connect(reply, &QNetworkReply::finished, this, processDownload);
    }