Slot-Funktion in QObject::connect wird nicht aufgerufen



  • Hallo,

    ich habe eine Frage zu Qt. Ich nutze QObject::connect() um eine Funktion, die ein OpenGL Frame rendert als Signal an einen Slot zu senden. Die Funktion im Slot macht ein Setup des Frames.

    //class VideoScreen.cpp
    m_emitFrame = new VideoScreenHelper(parent);
    //QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
    if (QObject::connect(m_emitFrame, &VideoScreenHelper::FrameChanged, this, &VideoScreen::HandleFrame, Qt::AutoConnection)) {
       qDebug() << "VideoScreen: connect function with frame transfer works";
    }
    
    void VideoScreen::HandleFrame(CComPtr<IDeckLinkVideoFrame> theFrame) {
       if (m_previewHelper != nullptr) {
          m_previewHelper->SetFrame(theFrame);
          qDebug() << "VideoScreen, HandleFrame: Set Frame was called."; //wird nicht ausgegeben.
       }
    }
    
    //class VideoScreenHelper.cpp
    /**
    * The method is called on every incoming frame and is derived from the IDeckLinkScreenPreviewCallback
    */
    HRESULT VideoScreenHelper::DrawFrame(IDeckLinkVideoFrame* frame) {
    	emit FrameChanged(CComPtr<IDeckLinkVideoFrame> (frame));
    	qDebug() << "VideoScreenHelper: emitting FrameChanged.";
    	return S_OK;
    }
    
    

    Beim Debuggen habe ich festgestellt das die Funktion HandleFrame anscheinend nicht aufgerufen wird, denn diese wird im Gegensatz zu allen anderen Funktionen nicht geloggt. Ich frage mich warum das nicht funktioniert, denn im Grunde ist das ja das Setup, dass das Qt Doc vorschlägt...?
    Anscheinend scheint es auch ein Problem mit dem Kontruktor von VideoScreen zu geben. Denn nach der Ausgabe von

    "VideoScreen: connect function with frame transfer works"
    

    werden zwei Threads mit Code 1 beendet. Alle anderen Funktionen werden wie geschrieben geloggt und die Threads mit Code 0 geschlossen. Meine Vermutung wäre das das an QObject::connect liegt?



  • Setze doch einfach einen Haltepunkt auf die erste Zeile von HandleFrame. Vllt. ist ja auch m_previewHelper nicht initialisiert?



  • @Th69

    Wenn ich den Breakpoint auf HandleFrame setze, ist die letzte Ausgabe "StartButton clicked. Stream is running". Die Initialisierung von dem Preview Helper funktioniert laut Log. Das wird ausgegeben:

    VideoScreen constructor was called.
    ScreenPreviewHelper installed.
    VideoScreen: connect function with frame transfer works
    Der Thread 0x2c5c hat mit Code 1 (0x1) geendet.
    Der Thread 0x2cc0 hat mit Code 1 (0x1) geendet.
    VideoScreen: GetScreenPeviewCallback was called.
    ControlVideo: ScreenPreviewCallback works.
    ControlVideo: SetCallback works.
    ControlVideo: Video input is enabled.
    ControlVideo: Audio input is enabled.
    ControlVideo: Stream has started.
    QVideoMeter: StartButton clicked. Stream is running.
    VideoScreenHelper: emitting FrameChanged.
    [...]
    VideoScreenHelper: emitting FrameChanged.
    Der Thread 0x3440 hat mit Code 0 (0x0) geendet.
    Der Thread 0x2790 hat mit Code 0 (0x0) geendet.
    ControlVideo: Stream has been stopped
    Der Thread 0x2450 hat mit Code 0 (0x0) geendet.
    Der Thread 0xd1c hat mit Code 0 (0x0) geendet.
    ControlVideo: Destructor is called.
    Der Thread 0x144c hat mit Code 1 (0x1) geendet.
    Der Thread 0x327c hat mit Code 0 (0x0) geendet.
    Der Thread 0x1468 hat mit Code 0 (0x0) geendet.
    [...]
    Der Thread 0x848 hat mit Code 0 (0x0) geendet.
    Das Programm "[13556] VideoMeter_Frontend.exe" wurde mit Code 0 (0x0) beendet.
    

    Intern sollte die Abwicklung des Streams damit eigentlich funktionieren. Die Einbindung mit Qt scheint Probleme zu machen.
    Ist das wohl ein Problem mit Multithreading?



  • Läuft VideoScreenHelper::DrawFrame in einem eigenen Thread? Und verwendest du dann einen QThread oder eine andere Implementierung?

    Folgende (englische) Artikel habe ich dazu gefunden: The Missing Article About Qt Multithreading in C++ sowie QT Multithread Signals and Slots



  • @Th69

    Ja, DrawFrame läuft in einem eigenen Thread hat dafür aber keine besondere Impementierung was Threading angeht. Ich hätte eher gedacht das QMutex die richtige Anlaufstelle ist.



  • Solange connect und emit in verschiedenen Threads ausgeführt werden, kommen die Signale nicht beim Slot an (dafür bietet QThread aber entsprechende Funktionalität [Stichwort: event loop], s. oben verlinkte Artikel).

    Wenn der DrawFrame-Thread aber nicht auf QThread beruht, dann wirst du andere Synchronisierungen benötigen (z.B. eine eigene thread-sichere Queue).

    In Emitting signal from a non-Qt Thread gibt es jedoch eine Antwort mittels QueuedConnection - probiere dies mal aus:

    QMetaObject::invokeMethod(receiver, "FrameChanged", Qt::QueuedConnection, Q_ARG(CComPtr<IDeckLinkVideoFrame>, frame));
    

    (oder so ähnlich, s. QMetaObject::invokeMethod)

    PS: Die verschiedenen Connection-Typen stehen in Threads and QObjects "Signals and Slots Across Threads".

    PPS: Beachte die Registrierung mittels qRegisterMetaType (laut Doku).



  • @Th69 Danke für den Hinweis.

    Ich habe versucht den EventLoop umzusetzen was beim ersten Versuch nicht gelungen ist. Habe dann alle Änderungen rükgängig gemacht und erhalte seit dem die folgenden drei Fehlermeldungen:
    Fehler C4430 Fehlender Typspezifizierer - int wird angenommen. Hinweis: "default-int" wird von C++ nicht unterstützt.
    Fehler C2143 Syntaxfehler: Es fehlt ";" vor "*" (Quelldatei wird kompiliert VideoScreenHelper.cpp)
    Fehler C2238 Unerwartete(s) Token vor ";" (Quelldatei wird kompiliert VideoScreenHelper.cpp)

    Alle Fehler betreffen Zeile 33 in der gleichen Datei.
    Dort steht:

    VideoScreenHelper*	m_emitFrame;
    

    Wo kommen diese Fehlermeldungen jetzt her? Wie geschrieben, es ist der Zustand wie vor den Änderungen.



  • Der Compiler scheint VideoScreenHelper nicht zu kennen. Fehlt #include "VideoScreenHelper.h"?

    Aber hast du mal QMetaObject::invokeMethod(...) statt dem emit ausprobiert?



  • @Th69

    VideoScreenHelper.h ist eingebunden. Eigentlich sollte IntelliSense ja die Instanz kennzeichnen, wenn diese nicht integriert ist.

    Ich hatte allerdings die Qt Versionen zwischenzeitlich gewechselt. Weil es ein Problem mit Qt-Header-Dateien in Version 5.15.1 und 6.1.2. gab. Könnte es daran liegen?

    QMetaObject::invokeMethod(...) habe ich noch nicht ausprobiert. Werde ich mir aber noch anschauen.



  • Fehler gefunden. Ich habe in der Hilfsklasse VideoScreen.h inkludiert und in VideoScreen.h ja wiederum die Hilfsklasse. 🙈



  • Die DrawFrame Methode sieht nun so aus und ich bin mir nicht sicher ob das so richtig ist:

    HRESULT VideoScreenHelper::DrawFrame(IDeckLinkVideoFrame* frame) {
    	QMetaObject::invokeMethod(this, "FrameChanged", Qt::QueuedConnection, Q_ARG(CComPtr<IDeckLinkVideoFrame>, frame));
    	qDebug() << "VideoScreenHelper: emitting FrameChanged.";
    	return S_OK;
    }
    

    Ich kann nicht prüfen ob das so richtig ist, da ich auf diese Weise einen Folgefehler erhalte, der mit connect aber nichts zu tun hat. Daher meine Frage.

    Und nach wie vor erhalte auf der Konsole nach Aufruf des Konstruktors VideoScreen die Meldung das Thread X und Thread Y mit Code 1 beendet wurden.



  • Ja, der Code sollte so passen.

    Und die Threads können ja auch von der externen Lib kommen (kannst ja im "Threads"-Fenster deiner IDE mal während des Debuggens überprüfen).



  • Klappt nach stundenlangem Debuggen leider nicht. Wenn ich mir die Thread-IDs ausgebe, dann sehe ich das der Konstruktor der Klasse VideoScreen und die Funktion DrawFrame (die steht in einer anderen Klasse) unterschiedliche IDs haben.
    Zusätzlich habe ich noch eine Klasse die die GUI-Main ist und mit der ich über Signal/Slot die Funktion aufrufe die letztlich das Video startet. In dieser müsste doch der Main-Thread laufen? Und diese läuft auch im gleichen Thread wie der Konstruktor von VideoScreen.
    Der Thread von DrawFrame() muss doch im gleichen Thread wie VideoScreen laufen?

    //VideoScreen.cpp
    VideoScreen::VideoScreen(QWidget* parent) :
    	m_previewHelper(nullptr)
    {
    	//Registering the type name of type CComPtr<IDeckLinkVideoFrame>, requires include<QMetaType>
    	//Create and destroy objects of the type dyamically at runtime after registration
    	qRegisterMetaType<CComPtr<IDeckLinkVideoFrame>>("CComPtr<IDeckLinkVideoFrame>");
    
    	m_vsThread = new QThread();
    	m_drawFrame = new VideoScreenHelper(parent);
    	m_drawFrame->moveToThread(m_vsThread);
    	//QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
    	QObject::connect(m_drawFrame, &VideoScreenHelper::FrameChanged, this, &VideoScreen::HandleFrame, Qt::QueuedConnection);
    	QObject::connect(m_vsThread, &QThread::finished, m_drawFrame, &QObject::deleteLater); //Deletes VideoScreenHelper object
    	QObject::connect(m_vsThread, &QThread::finished, m_vsThread, &QObject::deleteLater); //Deletes QThread object
    	m_vsThread->start();
    	qDebug() << "VideoScreen Ctor: Thread-ID is " << QThread::currentThreadId();
    }
    
    //QVideoMeter.cpp
    QVideoMeter::QVideoMeter(QWidget* parent)
        : QWidget(parent)
    {
        //initializes ControlVideo with a decklink instance
        m_control = std::make_unique<ControlVideo>(m_init->CreateDeckLinkInstance());
        m_videoScreen = new VideoScreen(parent);
        
        ui.setupUi(this);
        //Observer pattern in Qt style. If the start button is released, the video stream starts
        QObject::connect(ui.startBtn, &QPushButton::clicked, this, &QVideoMeter::QStartVideo);
        QObject::connect(ui.stopBtn, &QPushButton::released, this, &QVideoMeter::QStopVideo);
    
        qDebug() << "QVideoMeter CTOR" << QThread::currentThreadId();
    }
    


  • Genau dafür sollte eigentlich dann QueuedConnection sorgen, daß das Signal von einem Thread zu einem anderen gelangt (indem der andere [UI-]Thread diese in seiner Message-Loop verarbeitet) - also VideoScreen::HandleFrame sollte dann im UI-Thread ausgeführt werden (und nicht im gleichen Thread wie VideoScreenHelper::DrawFrame).

    Wenn aber immer noch nicht VideoScreen::HandleFrame dabei aufgerufen wird, dann solltest du mal testen, ob es wenigstens vom gleichen Thread (d.h. UI-Thread) aufgerufen werden kann (also das Signal mal per Button o.ä. per emit oder direkt per QMetaObject::invokeMethod aufrufen).



  • @Th69

    Der Thread vom Slot HandleFrame läuft im gleichen Thread wie die QVideoMeter (GUI-Klasse) und VideoScreen. Nur das Signal aus der Hilfsklasse läuft in einem eignen Thread. Das Vorschaubild in der GUI bleibt aber schwarz.
    So wie ich das verstehe, scheint mit dem Threading dann ja alles in Ordnung zu sein.



  • Ich denke das ganze Problem beruht nicht auf dem Threading, sondern darauf das bei der Speicherverwaltung irgendetwas schief läuft. Ich nutze jetzt in meinem gesamten Programm raw-Pointer und der Eventloop läuft. Allerdings wird der recht schnell, aber unregelmäßig abgebrochen. Fehlermeldungen sind entweder Critical error detected c0000374 oder 0xC0000005: Zugriffsverletzung beim Ausführen an Position xyz.
    Gibt es den Abbruch weil der Speicher vollläuft da die Zeiger nicht freigegeben werden.

    Wenn ich COM-Zeiger im Programm benutze, tritt dieses Problem nicht auf. Für diese ist ja aber auch die Release-Funktion implementiert.



  • Ich habe den Fehler gefunden. QOpenGLWidget::paintGL() wurde nur bei Änderungen an der GUI aufgerufen (z.B.: resizing). Wenn man paintGL() um die Funktion QWidget::update() ergänzt wird paintGL regelmäßig aufgerufen und das Videobild wird gerendert.
    Thema damit erledigt.


Anmelden zum Antworten