c++ - Qt 槽不在多线程 DLL 中执行

标签 c++ qt dll signals-slots

我正在开发一个动态加载的 DLL。在此 DLL 中,我需要使用一些 Qt 网络组件,如 QLocalServer 和 QNetworkAccessManager。要使用它们,我需要运行 Qt 事件循环。我读过 QLocalServer 可以在没有事件循环的情况下使用,但 QNetworkAccessManager AFAIK 并非如此。

我已经设法创建并执行了一个 QCoreApplication。 QCoreApplication 的实例化和执行是在同一个线程中完成的,我确保在使用任何其他 Qt 类之前创建 QCoreApplication。

DLL 还运行其他几个线程,当我从这些线程发出信号时,它们的连接插槽永远不会被调用,除非连接类型 = Qt::DirectConnection。我需要避免同步连接,所以我需要使用 Qt::QueuedConnection,对吗?

我提到的其他线程不是 QThread 的,它们是 std::thread 或 boost::thread 的。原因是这是共享代码,也需要在非 Qt 应用程序中运行。 信号以下列方式发出:我实例化一个从 QObject 派生的桥对象,并设置了 Q_OBJECT,因此 moc 编译器从中生成信号/槽代码。从这个桥接对象,我注册回调方法(使用升压信号)。当“其他”线程随后调用这些回调之一时,桥接对象会发出一个信号,该信号连接到同一类中的插槽。我的想法是槽然后从 Qt 事件循环中执行,这样我就可以开始异步使用 Qt 网络类。但是永远不会调用插槽。为什么?

我已经剥离了我的代码以在没有 DLL 的情况下重现问题。

main.cpp

#include "bridge.h"
#include "worker.h"

#include <QDebug>

#include <memory>
#include <iostream>
#include <string>

struct MyLibrary
{
public:
    MyLibrary()
    : myWorker_()
    , myQtBridge_(myWorker_)
    {
        myQtBridge_.start();
        myWorker_.start();
    }

private:
    MyWorker myWorker_;
    MyQtBridge myQtBridge_;
};

static std::shared_ptr<MyLibrary> myLibrary;


extern "C" __declspec(dllexport) void __cdecl start(void)
{
    try {
        myLibrary.reset(new MyLibrary());
    } catch (const std::exception& e) {
        qCritical() << e.what();
    }
}

extern "C" __declspec(dllexport) void __cdecl stop(void)
{
    try {
        myLibrary.reset();
    } catch (const std::exception& e) {
        qCritical() << e.what() << '\n';
    }
}


// main() is only here to reproduce the problem.
// In a DLL build, the calling application would call the start() and stop()
// functions.
int main(int argc, char *argv[])
{
    Q_UNUSED(argc);
    Q_UNUSED(argv);

    start();

    for (;;) {
        std::cerr << "Enter q to quit: ";
        std::string input;
        if (std::getline(std::cin, input) && input == "q") {
            break;
        }
    }

    stop();
}

bridge.h

#ifndef BRIDGE_H
#define BRIDGE_H

#include "worker.h"
#include "communicator.h"
#include "qapp.h"

// BOOST includes
#include <boost/bind.hpp>

// Qt includes
#include <QDebug>


class MyQtBridge
{
public:
    explicit MyQtBridge(MyWorker& myWorker)
    : myWorker_(myWorker) // copy reference to the worker
    , coreApplication_()  // instantiate QtCoreApplication and exec() it in a thread
    , myCommunicator_()   // instantiate my Qt communication module
    {
        myWorker_.onSignal1(boost::bind(&MyQtBridge::onSignal1Handler, this));
    }

    void start()
    {
        coreApplication_.start();
    }

private:
    void onSignal1Handler()
    {
        qDebug() << "MyQtBridge: calling myCommunicator_.signal1()";
        myCommunicator_.signal1();
        qDebug() << "MyQtBridge: called myCommunicator_.signal1()";
    }

private:
    MyWorker& myWorker_;
    CoreApplication coreApplication_; // Must be created before MyCommunicator!
    MyCommunicator myCommunicator_;
};


#endif // BRIDGE_H

worker.h

#ifndef WORKER_H
#define WORKER_H

// BOOST includes
#include <boost/signals2/signal.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

// STL includes
#include <memory>

// Qt includes
#include <QDebug>

// A dummy worker, just to reproduce the problem
// This code cannot have any dependencies to Qt ( except for QDebug now,... :-D )
class MyWorker
{
public:
    typedef boost::signals2::signal<void ()> signal_1_type;

    MyWorker()
    {
    }

    // called from main thread
    ~MyWorker()
    {
        try {
            if (thread_) {
                thread_->interrupt();
                thread_->join();
                qDebug() << "MyWorker thread joined";
                thread_.reset();
            }
        } catch (const std::exception& e) {
            qCritical() << e.what();
        }
    }

    boost::signals2::connection onSignal1(const signal_1_type::slot_type& subscriber)
    {
        return signal_1_.connect(subscriber);
    }

    void start()
    {
        if (!thread_) {
            thread_.reset(new boost::thread(boost::bind(&MyWorker::run, this)));
            qDebug() << "MyWorker thread created";
        }
    }

private:
    void run()
    {
        for (;;) {
            boost::this_thread::interruption_point();
            boost::this_thread::sleep(boost::posix_time::seconds(3));
            qDebug() << "MyWorker: calling signal_1_()";
            signal_1_();
            qDebug() << "MyWorker: called signal_1_()";
        }
    }

private:
    std::shared_ptr<boost::thread> thread_;
    signal_1_type signal_1_;

};

#endif // WORKER_H

qapp.h

#ifndef QAPP_H
#define QAPP_H

#include <QCoreApplication>
#include <QDebug>

#include <thread>
#include <mutex>
#include <condition_variable>

// Purpose of this class is to get a Qt event loop going.
// Instantiation of the QCoreApplication and calling it's exec() method
//  are both done in the same thread (seems to be a requirement).
// The rest of this class is synchronization.
class CoreApplication
{
public:
    CoreApplication()
    : thread_(&CoreApplication::run, this)
    {
        // Wait until the QCoreApplication has been created
        // This is needed before any other Qt objects are created.
        std::unique_lock<std::mutex> lock(mutex_);
        cv_app_created_.wait(lock);
    }

    CoreApplication(const CoreApplication&) = delete;

    ~CoreApplication()
    {
        QCoreApplication::instance()->quit();
        thread_.join();
    }

    void start()
    {
        cv_started_.notify_all();
    }

private:
    void run()
    {
        int argc = 0;
        char **argv = nullptr;

        QCoreApplication app(argc, argv);
        qDebug() << "QCoreApplication instantiated";
        cv_app_created_.notify_all();

        // Wait until we're started
        {
            std::unique_lock<std::mutex> lock(mutex_);
            cv_started_.wait(lock);
        }

        // blocking call, should return when QCoreApplication::instance()->quit() is called
        qDebug() << "CoreApplication:: calling QCoreApplication::exec()";
        app.exec();
        qDebug() << "CoreApplication:: called QCoreApplication::exec()";
    }

private:
    std::thread thread_;
    std::mutex mutex_;
    std::condition_variable cv_app_created_, cv_started_;
};

#endif // QAPP_H

communicator.h

#ifndef COMMUNICATOR_H
#define COMMUNICATOR_H

// Qt includes
#include <QObject>

// This would be the class that uses the Qt networking classes
// It would operate independently, reacting only to signals.
class MyCommunicator : public QObject
{
    Q_OBJECT
public:
    MyCommunicator();
    ~MyCommunicator();

    // called from MyQtBridge::onSignal1Handler()
    void signal1();

signals:
    void sigSignal1();

private slots:
    void slotSignal1();
};

#endif // COMMUNICATOR_H

communicator.cpp

#include "communicator.h"

// Qt includes
#include <QDebug>

MyCommunicator::MyCommunicator()
{
    // Note: the reason for this local signal connection is that
    // the signal sigSignal1() is emitted from a
    // different thread. The Qt::QueuedConnection flag should make sure that
    // the slot slotSignal1() is called in the QCoreApplication thread
    auto rc = connect(
      this, SIGNAL(sigSignal1())
    , this, SLOT(slotSignal1())
    , Qt::QueuedConnection
    );
    qDebug() << "MyCommunicator: connect: " << rc;
}

MyCommunicator::~MyCommunicator()
{
}

// called from MyQtBridge::onSignal1Handler()
void MyCommunicator::signal1()
{
    qDebug() << "MyCommunicator: emitting sigSignal1()";
    emit sigSignal1();
    qDebug() << "MyCommunicator: emitted sigSignal1()";
}

void MyCommunicator::slotSignal1()
{
    qDebug() << "MyCommunicator: slotSignal1(), yay!"; // NEVER CALLED!
}

最佳答案

在 code_fodder 提供的有用信息下找到了解决方案。

我使 MyCommunicator 成为 CoreApplication 的成员,在 CoreApplication::run 方法中将其实例化,并将 QCoreApplication 实例作为父实例,现在它可以工作了!

关于c++ - Qt 槽不在多线程 DLL 中执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37943631/

相关文章:

c++ - Qt 'Rectangle' 不是类型 - 当矩形被声明为类时

QT:两个布局添加相同的小部件

c++ - 为什么在复制对象时忽略顶级常量?

c++ - QCustomPlot - 在 customPlot 下方的 QCPAxisRect 上显示项目

c++ - STLPort、英特尔编译器、构建错误(尽管应用运行良好!)

java - 将第三方 dll 链接到我的 dll

c++ - gcnew Image CLI/C++ 时的无效操作

windows - 是否有与 -rpath 链接器标志等效的 Windows/MSVC?

c++ - 如何修复 'Cannot open include file ' boost/config.hpp':没有此类文件或目录(包括Dirs封面:C:\Program Files\boost\boost_1_67_0)

C++ & Allegro 4.2 - 我需要在窗口模式下拉伸(stretch)图形