2016-06-21 10 views
1

Ich entwickle eine DLL, die dynamisch geladen werden soll. In dieser DLL muss ich einige Qt-Netzwerkkomponenten wie QLocalServer und QNetworkAccessManager verwenden. Um diese zu verwenden, muss eine Qt-Ereignisschleife ausgeführt werden. Ich habe gelesen, dass QLocalServer ohne Ereignisschleife verwendet werden kann, aber das ist nicht der Fall mit QNetworkAccessManager AFAIK.Qt-Slots, die nicht in einer Multithread-DLL ausgeführt werden

Ich habe es geschafft, eine QCoreApplication zu erstellen und auszuführen. Instanziierung und Ausführung der QCoreApplication erfolgt im selben Thread und ich habe sichergestellt, dass die QCoreApplication erstellt wird, bevor eine andere Qt-Klasse verwendet wird.

Die DLL läuft auch mehrere andere Threads und wenn ich Signale von diesen Threads aussende, werden ihre verbundenen Slots niemals aufgerufen, außer wenn der Verbindungstyp = Qt :: DirectConnection ist. Ich muss synchrone Verbindungen vermeiden, also muss ich Qt :: QueuedConnection verwenden, richtig?

Die anderen Threads, die ich erwähnt habe, sind nicht QThread, sie sind std :: threads oder boost :: threads. Grund dafür ist, dass dies ein gemeinsamer Code ist, der auch in Nicht-Qt-Apps ausgeführt werden muss. Signale werden auf folgende Weise ausgegeben: Ich instanziiere ein Brückenobjekt, das von QObject und mit dem Q_OBJECT-Set abgeleitet ist, so dass der Moc-Compiler Signal-/Slot-Code daraus generiert. Von diesem Brückenobjekt aus registriere ich Callback-Methoden (mit Boost-Signalen). Wenn "andere" Threads dann einen dieser Callbacks aufrufen, gibt das Brückenobjekt ein Signal aus, das mit einem Slot derselben Klasse verbunden ist. Die Idee ist, dass der Slot dann von der Qt-Ereignisschleife ausgeführt wird, so dass ich beginnen kann, die Qt-Netzwerkklassen asynchron zu verwenden. Aber die Slots werden nie aufgerufen. Warum?

Ich habe meinen Code entfernt, um das Problem ohne die DLL-Zeug zu reproduzieren.

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

Wie läuft eine Ereignisschleife in MyCommunicator? - Ich sehe, dass Sie ein separates Objekt CoreApplication ausführen, aber das eine ist nicht verknüpft (oder auch nur nicht über das andere) ... Sie könnten eine 'QEventLoop loop;' Variable in Ihre MyCommunicator-Klasse einfügen und sie mit 'loop.exec() ausführen ; '... Das Problem scheint zu sein, dass Sie ein Signal (queuedconnction) übergeben, aber es wird niemals aufgerufen, weil keine Ereignisschleife läuft ... –

+0

Alternativ können Sie möglicherweise die CoreApplication (Zeiger) als "Eltern" Ihres MyCommunicator übergeben und diese an das geerbte QObject übergeben, das QObject als Parameter "parent" verwendet. Dies setzt voraus, dass Sie CoreApplication-Klasse ist eine (oder erbt) QCoreApplication? –

+0

Du hast es selbst gesagt - die anderen Threads, die du verwendest, sind keine QThreads - also haben sie keine Eventloop und deine Slots können nicht aufgerufen werden. Verwenden Sie entweder QThread oder erstellen Sie eine benutzerdefinierte Eventloop. – Felix

Antwort

0

die Lösung dank der hilfreichen Informationen von code_fodder bereitgestellt gefunden.

Ich habe den MyCommunicator zu einem Member von CoreApplication gemacht, instanziiere ihn in der CoreApplication :: run-Methode mit der QCoreApplication-Instanz als Parent und jetzt funktioniert es!