QApplication in a std::thread that lives in a DLL
-
Hello everybody,
I'm still pretty much at the beginning with QT. But before I can get into it, I have to know if what I have in mind will work.
We have an application which based on a very old compiler and framwork. The idea is port step by step the GUI and the logic to QT
by using a mordern compiler.
For this I made DLL and start QApplication in a std::thread. This looks like this:static std::thread * g_qAppThread = nullptr; static QApplication *g_qApp = nullptr; static GuiLauncher * g_guiLauncher = nullptr; void threadFunc() { static int argc = 1; const static char *argv[] = { "thread", nullptr }; g_qApp = new QApplication( argc, const_cast<char**>(argv)); g_qApp->exec(); delete g_qApp; g_qApp = nullptr; std::cout << "threadFunc() end" << std::endl; } QT_IN_DLLSHARED_EXPORT int startQtApplication() { if( g_qAppThread != nullptr ) { std::cout << "startApplication(): thread already running" << std::endl; return -1; } g_qAppThread = new std::thread( &threadFunc ); // Now wait until the EventDispatcher of QApplication is running. using namespace std::chrono_literals; while( g_qApp == nullptr || ! g_qApp->eventDispatcher() ) std::this_thread::sleep_for( 10ms ); g_qAppThread->detach(); g_qApp->setQuitOnLastWindowClosed( false ); g_guiLauncher = new GuiLauncher; g_guiLauncher->moveToThread( QApplication::instance()->thread() ); return 1; }
QT_IN_DLLSHARED_EXPORT void showTestDialog( const char * name, const char * vorname ) { if( g_qApp == nullptr ) startQtApplication(); g_qApp->postEvent( g_guiLauncher, new TestDialogEvent( name, vorname, EventWrapper::instance().getEventType( Functions::TEST_DIALOG ) ) ); }
My Test is to open a QMessageBox from a DLL function call (see above showMessageBox()).
In the internet I found several postings with the same problem. But most answers did not work. The only way I get it to work
is to use QApplication::postEvent().For that implement the class GuiLauncher.
class GuiLauncher : public QObject { Q_OBJECT public: GuiLauncher(){}; ~GuiLauncher() override {}; bool event( QEvent * ev ) override { if( ev->type() == EventWrapper::instance().getEventType( Functions::TEST_DIALOG ) ) { TestDialogEvent * testDialogEvent = dynamic_cast< TestDialogEvent *>( ev ); showTestDialog( testDialogEvent->getName(), testDialogEvent->getVorname() ); } return false; } private: void showTestDialog( const QString & name, const QString & vorname ); { TestDialog * dlg = new TestDialog( nullptr, name, vorname ); dlg->setModal( true ); dlg->setAttribute( Qt::WA_DeleteOnClose, true ); dlg->show(); } };
The class TestDialog inherit from QDialog and has two LineEdit controls.
This all works. But I find it very cumbersome to let everything run through events.
Is there another way? On the Internet I have found solutions using QMetaObject::invokeMethod().
But that didn't work for me. The method was not called. I also found solutions using
signal/slot. But even that didn't work for me.The next problem is that the DLL function showTestDialog() should wait for the dialog to close.
I don't have a solution yet.Anybody got an idea?
-
@elinmerl said in QApplication in a std::thread that lives in a DLL:
We have an application which based on a very old compiler and framwork.
Which one? If it is Visual studio with MFC, there is a possibility for the migration: https://docs.huihoo.com/qt/solutions/4/qtwinmigrate/winmigrate-walkthrough.html
Otherwise you should give some more information about the old app.
Is logic and GUI already separated? That would make things easier...
-
@elinmerl said in QApplication in a std::thread that lives in a DLL:
On the Internet I have found solutions using QMetaObject::invokeMethod().
But that didn't work for me. The method was not called. I also found solutions using
signal/slot. But even that didn't work for me.These can definitely be made to work. Post your code and we'll have a look.
The next problem is that the DLL function showTestDialog() should wait for the dialog to close.
I don't have a solution yet.Once you get
QMetaObject::invokeMethod()
working, you can use aQt::BlockingQueuedConnection
to block the caller until the invoked function returns.Some other improvements:
g_qApp = new QApplication( argc, const_cast<char**>(argv));
No need to use
new
, just create QApplication on the stack.g_qApp->setQuitOnLastWindowClosed( false );
You're calling this from a different thread to the one that QApplication lives in. That's not supported; instead, call this in
startQtApplication()
-
@aha_1980 said in QApplication in a std::thread that lives in a DLL:
@elinmerl said in QApplication in a std::thread that lives in a DLL:
We have an application which based on a very old compiler and framwork.
Which one? If it is Visual studio with MFC, there is a possibility for the migration: https://docs.huihoo.com/qt/solutions/4/qtwinmigrate/winmigrate-walkthrough.html
Otherwise you should give some more information about the old app.
The app is build with Borland C++ 5.02 Compiler. And the GUI is built with Borland-OWL.
Is logic and GUI already separated? That would make things easier...
The GUI is not separated from the logic. That's why we want to move everything step by step into DLLs and replace the GUI with QT at the same time.
-
@JKSH said in QApplication in a std::thread that lives in a DLL:
@elinmerl said in QApplication in a std::thread that lives in a DLL:
On the Internet I have found solutions using QMetaObject::invokeMethod().
But that didn't work for me. The method was not called. I also found solutions using
signal/slot. But even that didn't work for me.These can definitely be made to work. Post your code and we'll have a look.
The next problem is that the DLL function showTestDialog() should wait for the dialog to close.
I don't have a solution yet.Once you get
QMetaObject::invokeMethod()
working, you can use aQt::BlockingQueuedConnection
to block the caller until the invoked function returns.Today I found a solution that works with QMetaObject::invokeMethod(). But it's also expensive. So my new showTestDialog() function looks like that:
QT_IN_DLLSHARED_EXPORT void showTestDialog( HWND parent, const char * name, const char * vorname ) { if( g_qApp == nullptr ) startQtApplication(); QThread thread; QEventLoop loop; QObject context; context.moveToThread( &thread ); QObject::connect( &thread, &QThread::started, &context, [&]() { std::cout << "QThread started" << std::endl; bool ret = QMetaObject::invokeMethod( g_guiLauncher, "showTestDialog", Qt::BlockingQueuedConnection, Q_ARG( HWND, parent), Q_ARG( QString, name), Q_ARG( QString, vorname ) ); std::cout << "showTestDialog(): invokeMethod returned (" << ret << ")" << std::endl; loop.quit(); }); thread.start(); loop.exec(); thread.quit(); thread.wait(); std::cout << "showTestDialog(): after postEvent()" << std::endl; }
Some other improvements:
g_qApp = new QApplication( argc, const_cast<char**>(argv));
No need to use
new
, just create QApplication on the stack.I use it to check if there is a QApp running ist I call a DLL function.
-
@elinmerl said in QApplication in a std::thread that lives in a DLL:
No need to use
new
, just create QApplication on the stack.I use it to check if there is a QApp running ist I call a DLL function.
You can take a pointer to a stack-allocated object.
Anyway, Qt already provides a global pointer to your QApplication:
qApp
orQApplication::instance()
. Furthermore, these pointers are set tonullptr
before QApplication is constructed and after it's destroyed.Note: There's a minor race condition when you check for
== nullptr
or!= nullptr
from another thread, but if you're careful it works well.Today I found a solution that works with QMetaObject::invokeMethod(). But it's also expensive. So my new showTestDialog() function looks like that:
Congrats on finding a solution. Let's simplify things further -- You don't need to construct the QThread, QEventLoop, or QObject:
// Code that runs in the new thread static GuiLauncher * g_guiLauncher = nullptr; static void threadFunc() { static int argc = 1; const static char *argv[] = { "thread", nullptr }; QApplication app( argc, const_cast<char**>(argv) ); app.setQuitOnLastWindowClosed( false ); // You MUST call QApplication methods in the QApplication thread GuiLauncher launcher; g_guiLauncher = &launcher; // You can get a pointer to a stack-allocated object app.exec(); g_guiLauncher = nullptr; std::cout << "threadFunc() end" << std::endl; }
// Code that initializes the new thread static int startQtApplication() { if (qApp != nullptr) { std::cout << "startApplication(): thread already running" << std::endl; return -1; } std::thread t(&threadFunc); // Again, you don't need `new` t.detach(); // Now wait until the GuiLauncher has been created // NOTE: `while (qApp->eventDispatcher() == nullptr)` is not enough! // Because the event dispatcher is created before GuiLauncher. using namespace std::chrono_literals; while (g_guiLauncher == nullptr) std::this_thread::sleep_for( 10ms ); return 1; }
// Code that you call from your main thread // NOTE: I've removed the HWND parameter for clarity QT_IN_DLLSHARED_EXPORT void showTestDialog( const char * name, const char * vorname ) { // WARNING: If this function is called 2x rapidly, the following check will give wrong results if (qApp == nullptr) startQtApplication(); // You can use your GuiLauncher as the Context object bool ret = QMetaObject::invokeMethod(g_guiLauncher, [=]() { // ASSUMPTION: The method is public and its signature is // GuiLauncher::showTestDialog(const QString &name, const QString &vorname) g_guiLauncher->showTestDialog(QString::fromLatin1(name), QString::fromLatin1(vorname)); }, Qt::BlockingQueuedConnection); std::cout << "showTestDialog(): invokeMethod returned (" << ret << ")" << std::endl; }