2016-06-21 5 views
1

Я разрабатываю DLL, которая должна быть загружена динамически. В этой DLL мне нужно использовать некоторые сетевые компоненты Qt, такие как QLocalServer и QNetworkAccessManager. Чтобы использовать их, мне нужно запустить цикл событий Qt. Я читал, что QLocalServer можно использовать без цикла событий, но это не относится к QNetworkAccessManager AFAIK.Слоты Qt не выполняются в многопоточной DLL

Мне удалось создать и выполнить QCoreApplication. Выполнение и выполнение QCoreApplication выполняется в одном потоке, и я убедился, что QCoreApplication создается до того, как будет использован любой другой класс Qt.

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! 
} 
+0

Как работает цикл событий в MyCommunicator? - Я вижу, что вы запускаете отдельный объект CoreApplication, но один из них не связан (или даже не знает) другого ... Вы можете добавить цикл QEventLoop; varaible в свой класс MyCommunicator и запустить его с помощью 'loop.exec() ; ... Проблема заключается в том, что вы передаете сигнал (queuedconnction), но он никогда не будет вызван, потому что нет цикла событий ... –

+0

В качестве альтернативы вы можете передать CoreApplication (указатель) в качестве «родителя» вашего MyCommunicator и передать это до унаследованного QObject, который принимает QObject как параметр «parent» ... это предполагает, что ваш Класс CoreApplication является (или наследует) QCoreApplication? –

+0

Вы сказали это сами: другие темы, которые вы используете, не являются QThreads, поэтому у них нет eventloop, и ваши слоты cannont не будут вызваны. Либо используйте QThread, либо создайте специальный eventloop. – Felix

ответ

0

найдено решение, благодаря полезной информации, предоставленной code_fodder.

Я сделал MyCommunicator членом CoreApplication, создав его в методе CoreApplication :: run с экземпляром QCoreApplication как родительским, и теперь он работает!