如何使用 QNetworkAccessManager 作为 QT DLL 函数下载文件?

Posted

技术标签:

【中文标题】如何使用 QNetworkAccessManager 作为 QT DLL 函数下载文件?【英文标题】:How can i download a file using QNetworkAccessManager as a QT DLL function? 【发布时间】:2021-02-23 12:12:08 【问题描述】:

我正在尝试创建一个 QT DLL 以在 InnoSetup 安装程序中使用它(InnoSetup 是用 Delphi Pascal 编写的)。

当从 InnoSetup 调用时,该 DLL 应该具有从 Internet 下载文件的功能。

InnoSetup 调用如下:

procedure downloadFile();
  external '_ZN9testClass10doDownloadEv@files:classTest.dll stdcall delayload';

然后我用这个来称呼它:

function InitializeSetup(): Boolean;
begin
  Result := True;
  ExtractTemporaryFile('classTest.dll');
  downloadFile();
end;

我已经在我的 DLL 中使用一个简单的函数进行了尝试,它正在工作。下面是测试函数:

extern "C" __declspec(dllexport) void testClass::testFunction()

    QFile file("output.txt");
    file.open(QIODevice::WriteOnly | QIODevice::Text);
    QTextStream out(&file);
    out << "Function call is working!";
    file.close();

现在,我尝试在我的 DLL 中实现一个函数,该函数将使用 QNetworkAccessManager 和 QThreads 下载文件。可悲的是,由于某种我无法理解的原因,这不起作用。

这是我的 DLL 代码:


-- TESTDLL.H --

#ifndef TESTDLL_H
#define TESTDLL_H

#include <QtCore>

#include "testdll_global.h"
#include "libs/downloader.h"

class TESTCLASS_EXPORT testClass : public QObject

    Q_OBJECT
  public:
    void doDownload();
    void handleResults();
  public slots:
  private:
;

#endif // TESTDLL_H


-- TESTDLL_GLOBAL.H --

#ifndef TESTDLL_GLOBAL_H
#define TESTDLL_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(TESTCLASS_LIBRARY)
#  define TESTCLASS_EXPORT Q_DECL_EXPORT
#else
#  define TESTCLASS_EXPORT Q_DECL_IMPORT
#endif

#endif // TESTDLL_GLOBAL_H


-- TESTDLL.CPP --

#include "testdll.h"

extern "C" __declspec(dllexport) void testClass::doDownload()

     downloadWorker *ts_testDownloadWorker = new downloadWorker(this);
     connect(ts_testDownloadWorker, &downloadWorker::finished, ts_testDownloadWorker, &QObject::deleteLater);
     connect(ts_testDownloadWorker, &downloadWorker::resultReady, this, &testClass::handleResults);
     ts_testDownloadWorker->Execute();


void testClass::handleResults()

    QFile file("result.txt");
    file.open(QIODevice::WriteOnly | QIODevice::Text);
    QTextStream out(&file);
    out << "Passed!";
    file.close();



-- DOWNLOADER.H --

#ifndef DOWNLOADER_H
#define DOWNLOADER_H

#include <QtNetwork>
#include <QDebug>

class downloadWorker : public QThread

    Q_OBJECT
 signals:
    void resultReady(const QString &s);

 public:
    downloadWorker(QObject *parent);
    ~downloadWorker();
    void Execute();
    void saveToDisk(QString fileName, QByteArray content);

 protected:
    void run();

 private:
    bool m_abort;
    QNetworkAccessManager *networkMgr;
    QNetworkReply *replyNetworkSmall;

  public slots:
    void startDownload (QString url, QString fileName);
    QByteArray prepareDownload(QString &url);
    void downloadFinished(QNetworkReply *reply);
    void replyNetworkSmallError();
;

#endif // DOWNLOADER_H


-- DOWNLOADER.CPP --

#include "downloader.h"

downloadWorker::downloadWorker(QObject *parent)
    : QThread(parent)

    m_abort = false;


downloadWorker::~downloadWorker()

   m_abort = true;
   wait();


void downloadWorker::Execute()

    m_abort = false;
    start();


void downloadWorker::run()

    QString result;

    startDownload("http://www.google.com", "download.txt");
    exec();

    emit resultReady(result);


void downloadWorker::startDownload (QString url, QString fileName)

    QByteArray downloadUrl = prepareDownload(url);
    saveToDisk(fileName, downloadUrl);


QByteArray downloadWorker::prepareDownload(QString &url)

    QNetworkAccessManager *networkMgr = new QNetworkAccessManager;
    connect(networkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));

    QNetworkRequest request;
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    request.setUrl(url);
    replyNetworkSmall = networkMgr->get(request);
    QEventLoop loop;
    connect(replyNetworkSmall, SIGNAL(finished()), &loop, SLOT(quit()));
    connect(replyNetworkSmall, SIGNAL(error(QNetworkReply::NetworkError)),
                this, SLOT(replyNetworkSmallError()));
    loop.exec();

    QByteArray bts = replyNetworkSmall->readAll();
    return bts;


void downloadWorker::saveToDisk(QString fileName, QByteArray content)

    QFile mfile(fileName);

    if (!mfile.open(QFile::ReadWrite))
        
            mfile.close();
            QFile::remove(fileName);
        
        else
        
            mfile.write(content);
            mfile.flush();
            mfile.close();
        


void downloadWorker::downloadFinished(QNetworkReply *reply)

    reply->deleteLater();
    this->exit();


void downloadWorker::replyNetworkSmallError()

    if(replyNetworkSmall->error())
    
        //errorSmallDownload(replyNetworkSmall->errorString());
        //downloadError = true;
    
    replyNetworkSmall->deleteLater();



-- TESTDLL.pro --

QT -= gui
QT += network

TEMPLATE = lib
DEFINES += TESTCLASS_LIBRARY

CONFIG += c++11 dll

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    testdll.cpp \
    libs\downloader.cpp

HEADERS += \
    testdll_global.h \
    testdll.h \
    libs\downloader.h

QMAKE_LFLAGS += -Wl,--output-def,testdll.def

# Default rules for deployment.
unix 
    target.path = /usr/lib

!isEmpty(target.path): INSTALLS += target

如果我尝试调用 doDownload 函数,则没有下载文件,这就像我的 dll 中没有执行循环来实际完成这项工作一样。但是,如果我将此代码转换为应用程序并使用带有 exect 的 main,则文件正在下载。

我还能对上面的代码做些什么来真正管理下载文件?我有什么遗漏吗?

提前谢谢各位!

PS:我完全知道 InnoSetup 实现了自己的文件下载器功能。但是,我想使用我自己的 DLL 中的下载功能;)

解决方案

这是我的问题的解决方案,基于@MSalters 的回答:


-- TESTDLL.H --

#ifndef TESTDLL_H
#define TESTDLL_H

#include <QtCore>

#include "testdll_global.h"
#include "libs/downloader.h"

class testClass : public QObject

    Q_OBJECT
  public:
    void doDownload();
    void handleResults();
  public slots:
  private:
;

#endif // TESTDLL_H


-- TESTDLL.CPP --

#include "testdll.h"

namespace QCoreAppDLL

    static int argc = 1;
    static char * argv[] = (char *)"testdll.cpp", nullptr;
    static QCoreApplication * pApp = nullptr;


extern "C" TESTCLASS_EXPORT void initDLL()

    if (!QCoreApplication::instance())
    
        QCoreAppDLL::pApp = new QCoreApplication(QCoreAppDLL::argc, QCoreAppDLL::argv);

        testClass w;
        w.doDownload();

        QCoreAppDLL::pApp->exec();
    


void testClass::doDownload()

     downloadWorker *ts_testDownloadWorker = new downloadWorker(this);
     connect(ts_testDownloadWorker, &downloadWorker::finished, ts_testDownloadWorker, &QObject::deleteLater);
     connect(ts_testDownloadWorker, &downloadWorker::resultReady, this, &testClass::handleResults);
     ts_testDownloadWorker->Execute();


void testClass::handleResults()

    if (QCoreAppDLL::pApp)
        QCoreAppDLL::pApp->quit();

【问题讨论】:

"外部'_ZN9testClass10doDownloadEv"。您可能想阅读extern "C"。 C 没有类或类函数,因此您不会为 testClass::testFunction 获得一个好的 C 名称。 正确,在我修复我的 dll 的主要问题后,我会这样做,干杯! "(char *)"testdll.cpp" 这看起来很危险。命令行解析器删除它处理的参数并不是闻所未闻的,当argv 是可写的(如通常是)。 想要抑制“警告:ISO C++11 不允许从字符串文字转换为 'char *'”警告。有没有其他方法可以抑制它? 有点跑题了,但为了记录,有一个现有的 Inno Setup 插件可以通过 HTTP(S) 下载文件:mitrichsoftware.wordpress.com/inno-setup-tools/… 【参考方案1】:

正如您所说,在普通的 Qt 应用程序中,这将起作用。那是因为您的 QApplication 对象处理了 QCoreApplication::notify 函数。在您的 DLL 中,您不需要完整的 QApplication,但您仍然需要至少一个 QCoreApplication

【讨论】:

谢谢,我找到了一种方法来重新实现你的建议,现在它正在工作。发布了固定代码,其他人也可以从中受益。

以上是关于如何使用 QNetworkAccessManager 作为 QT DLL 函数下载文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等

如何使用 Math.Net 连接矩阵。如何使用 Math.Net 调用特定的行或列?

WSARecv 如何使用 lpOverlapped?如何手动发出事件信号?