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 auchm_previewHelper
nicht initialisiert?
-
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 einenQThread
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
-
Ja,
DrawFrame
läuft in einem eigenen Thread hat dafür aber keine besondere Impementierung was Threading angeht. Ich hätte eher gedacht dasQMutex
die richtige Anlaufstelle ist.
-
Solange
connect
undemit
in verschiedenen Threads ausgeführt werden, kommen die Signale nicht beim Slot an (dafür bietetQThread
aber entsprechende Funktionalität [Stichwort: event loop], s. oben verlinkte Artikel).Wenn der
DrawFrame
-Thread aber nicht aufQThread
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 dememit
ausprobiert?
-
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 inVideoScreen.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 FunktionDrawFrame
(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) - alsoVideoScreen::HandleFrame
sollte dann im UI-Thread ausgeführt werden (und nicht im gleichen Thread wieVideoScreenHelper::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.ä. peremit
oder direkt perQMetaObject::invokeMethod
aufrufen).
-
Der Thread vom Slot
HandleFrame
läuft im gleichen Thread wie dieQVideoMeter
(GUI-Klasse) undVideoScreen
. 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
oder0xC0000005: 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 manpaintGL()
um die FunktionQWidget::update()
ergänzt wirdpaintGL
regelmäßig aufgerufen und das Videobild wird gerendert.
Thema damit erledigt.