vendredi 24 juin 2016

Qt slots not executed in a multi-threaded DLL

I am developing a DLL that is to be loaded dynamically. In this DLL I need to use some Qt networking components, like QLocalServer and QNetworkAccessManager. To use those, I need to have a Qt event loop running. I've read that QLocalServer could be used without an event loop, but that's not the case with QNetworkAccessManager AFAIK. I've managed to create and exec a QCoreApplication. Instantiation and execution of the QCoreApplication is done in the same thread and I've made sure that the QCoreApplication is created before any other Qt class is used. The DLL runs several other threads as well and when I emit signals from those threads, their connected slots are never called, except if connection type = Qt::DirectConnection. I need to avoid synchronous connections, so I need to use Qt::QueuedConnection, right? The other threads I've mentioned are not QThread's, they are std::thread's or boost::thread's. Reason for it is that this is shared code that needs to run in non-Qt apps as well. Signals are emitted in the following way: I instantiate a bridge object derived from QObject and with the Q_OBJECT set, so the moc compiler generates signal/slot code from it. From this bridge object, I register callback methods (using boost signals). When 'other' threads then call one of these callbacks, the bridge object then emits a signal, that is connected to a slot in the same class. The idea is that the slot is then executed from the Qt event loop so I can start using the Qt networking classes asynchronously. But the slots are never called. Why? I've stripped my code to reproduce the problem without the DLL stuff. 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! }

Aucun commentaire:

Enregistrer un commentaire