如何正确关闭在其他线程上创建的QWidget? 【OBS源码插件】

问题描述 投票:0回答:1

我正在尝试为 OBS Studio 制作一个插件,它将添加自定义源类型对象。

OBS 提供了一种简单的方法让用户使用函数 get_properties() 显示和更改源属性,但遗憾的是这对于我的插件来说还不够。相反,我创建一个全新的 QWidget 作为我的源属性主窗口,并关闭 OBS studio 打开的默认窗口。

更准确地说,我正在做以下事情:

  1. 当源调用
    get_properties()
    时,它将创建一个新的
    GUIHandler
    (如果尚不存在)并调用
    GUIHandler::show()
  2. GUIHandler::show()
    将关闭OBS Studio创建的QWidget(我使用
    QApplication::topLevelWindows()
    来找到它),然后将创建并显示我的自定义OBSGraphicsMainWindow(继承自QWidget)

当然,我的 OBSGraphicsMainWindow 可以访问源对象中存储的一些变量,以便读取和写入一些值。

到目前为止,一切工作正常,但当我需要删除窗口时,问题就开始了。基本上,删除窗口有以下三种情况:

  1. 当用户单击 OBSGraphicsMainWindow 关闭按钮时。
  2. 当用户从场景中删除源时,链接到它的 OBSGraphicsMainWindow 将打开。
  3. 当OBS Studio关闭时(即:用户按下OBS Studio主窗口关闭按钮)并且OBSGraphicsMainWindow被打开。

第一个选项不是问题,但我找不到在

GUIHandler
析构函数中删除窗口的好方法,每当源被销毁时都会调用该析构函数。

这就是我到目前为止所做的:

GUIHandler::~GUIHandler()
{
    if (window != nullptr) {
        window->onDeletion = nullptr;
        window->deleteLater();
    }
}

这是有点工作,但问题是

deleteLater()
会在非线程安全实现中删除OBSGraphicsMainWindow,有时它会尝试访问已删除的源对象变量,这可能让 OBS 崩溃。

我更愿意使用

delete window;
来代替,但据我所知,我无法这样做,因为我的 OBSGraphicsMainWindow QWidget 不是在与 OBS Studio QWidget 相同的线程上创建的。因此,
destroyed()
信号将由OBS Studio线程发出,但接收器是我的源线程,这在Qt中是不允许的:

QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到不同线程拥有的对象。当前线程 0x0x2807a1a5060。接收器“”(类型为“QWidget”)是在线程 0x0x2807457c590 中创建的”

如果我尝试使用

window->close()

,程序将永远挂起并冻结。



问题:

当需要从 OBS Studio 主线程关闭时,您认为删除在另一个线程中创建的自定义 OBSGraphicsMainWindow QWidget 对象的最佳方法是什么?我是否应该将 deleteLater() 与两个线程共享的

std::mutex
一起使用,并让线程在删除源对象之前等待删除所有 Qt 对象?
我不得更改 OBS Studio 代码本身。
提前感谢您的回答!

编辑:

我更改了

GUIHandler

析构函数。这是课程:


    .h:
  • #pragma once #include <functional> #include <mutex> class OBSGraphicsMainWindow; class OBSBlueprintGraph; class GUIHandler { public: GUIHandler(OBSBlueprintGraph* g); ~GUIHandler(); void show() const; private: std::condition_variable cond{}; OBSGraphicsMainWindow* window = nullptr; OBSBlueprintGraph* graph;
    .cpp:
  • #include "gui-graph.h" #include <iostream> #include <QApplication> #include <QScreen> #include <QWindow> #include "obs-frontend-api.h" #include "obs-graphics-main-window.h" #include "Core/obs-blueprint-graph.h" #include "Helpers/global-logger.h" GUIHandler::GUIHandler(OBSBlueprintGraph *g) : graph(g) { QWidget *parent = static_cast<QWidget *>(obs_frontend_get_main_window()); window = new OBSGraphicsMainWindow(graph, parent, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); window->deleteComplete = [this] { GDebug("[GUI] >> Main Window DESTROYED! <<"); window = nullptr; cond.notify_all(); }; } GUIHandler::~GUIHandler() { if (window != nullptr) { std::mutex mtx{}; std::unique_lock lock{mtx}; window->deleteLater(); cond.wait_for(lock, std::chrono::milliseconds(100)); // TODO improve this. For now GUI is still deleted AFTER graph if closed if (window != nullptr) { window->context().onDeletion.execute(); } } } void GUIHandler::show() const { // Hide default OBS properties window, TODO how to do it better? maybe even prevent properties window creation? const QWindowList list = QApplication::topLevelWindows(); // Reverse list loop for better performance QList<QWindow*>::const_iterator it = list.constEnd(); while(it != list.constBegin()) { --it; if((*it)->objectName() == "OBSBasicPropertiesWindow") { (*it)->close(); break; } } window->adjustSize(); // Size to content // Center window on screen QRect screenRect = QApplication::primaryScreen()->geometry(); QRect windowRect = window->geometry(); window->move((screenRect.width() - windowRect.width())/2, (screenRect.height() - windowRect.height())/2); window->show(); }

OBSGraphicsMainWindow

 删除其所有子 QObject 时,会调用 std::function<void()> deleteComplete
    
	

multithreading qt mutex qwidget obs
1个回答
0
投票

condition_variable 结合 OBS 前端 API 回调 来解决我的问题 如前所述,我不能简单地在

delete window;

析构函数中使用

~GUIHandler()
,因为这样当通过
obs_source_info
destroy() 函数 ptr 调用析构函数时,我会收到以下错误:

QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到不同线程拥有的对象。当前线程 0x0x2807a1a5060。接收器“”(类型为“QWidget”)是在线程 0x0x2807457c590 中创建的”

为了使用

deleteLater()

并等待

OBSGraphicsMainWindow删除,我实现了一个condition_variable,它将在调用deleteLater()后立即等待。另一方面,当
OBSGraphicsMainWindow被删除时,会通知condition_variable。
从场景中删除 OBS 源时,此功能运行良好,但如果程序关闭,则会导致程序永远挂起并冻结。在深入研究 OBS Studio 后,我发现 

OBS Frontent API

在源被销毁之前发送了 OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN 类型的事件(请参阅

OBSBasic::closeEvent(),第 4879 行)。因为 OBS Frontend API 使用 Qt 线程,所以我能够向事件添加回调并使用 delete window; 直接删除 OBSGraphicsMainWindow

obs-graphics-main-window.h:

class OBSGraphicsMainWindow : public QWidget { public: OBSGraphicsMainWindow(OBSBlueprintGraph* g, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~OBSGraphicsMainWindow() override; std::function<void()> onDestructorEnd; private: ... };

obs-graphics-main-window.cpp:

OBSGraphicsMainWindow::OBSGraphicsMainWindow(OBSBlueprintGraph *g, QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), graph(g) { ... } OBSGraphicsMainWindow::~OBSGraphicsMainWindow() { delete layout; ... if (onDestructorEnd) onDestructorEnd(); }

gui-handler.h:

class GUIHandler { public: GUIHandler(OBSBlueprintGraph* g); ~GUIHandler(); static void OBSEvent(enum obs_frontend_event event, void* ptr); void show() const; private: std::condition_variable cond{}; OBSGraphicsMainWindow* window = nullptr; OBSBlueprintGraph* graph; };

gui-handler.cpp:

GUIHandler::GUIHandler(OBSBlueprintGraph *g) : graph(g) { obs_frontend_add_event_callback(OBSEvent, this); QWidget *parent = static_cast<QWidget *>(obs_frontend_get_main_window()); window = new OBSGraphicsMainWindow(graph, parent, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); window->onDestructorEnd = [this] { GDebug("[GUI] OBS Blueprint main window Destroyed!"); window = nullptr; cond.notify_all(); }; } GUIHandler::~GUIHandler() { obs_frontend_remove_event_callback(OBSEvent, this); if (window != nullptr) { GDebug("[GUI] Destroy OBS BP Main window using deleteLater() and lock"); std::mutex mtx{}; std::unique_lock lock{mtx}; window->deleteLater(); cond.wait_for(lock, std::chrono::seconds(1)); } } void GUIHandler::OBSEvent(enum obs_frontend_event event, void *ptr) { if (event == OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN) { GDebug("[GUI] Destroy OBS BP Main window using OBS Frontend API Event"); GUIHandler *handler = static_cast<GUIHandler *>(ptr); delete handler->window; } } void GUIHandler::show() const { // Hide default OBS properties window, TODO how to do it better? maybe even prevent properties window creation? const QWindowList list = QApplication::topLevelWindows(); // Reverse list loop for better performance QList<QWindow*>::const_iterator it = list.constEnd(); while(it != list.constBegin()) { --it; if((*it)->objectName() == "OBSBasicPropertiesWindow") { (*it)->close(); break; } } window->adjustSize(); // Size to content // Center window on screen QRect screenRect = QApplication::primaryScreen()->geometry(); QRect windowRect = window->geometry(); window->move((screenRect.width() - windowRect.width())/2, (screenRect.height() - windowRect.height())/2); window->show(); }

© www.soinside.com 2019 - 2024. All rights reserved.