In an effort to create a Qt event loop in a separate thread, from within a DLL which is called by a main application written in Java, I have done the following, based on a suggestion I read here, which works rather well:
// Define a global namespace. We need to do this because the parameters
// passed to QCoreApplication must have a lifetime exceeding that of the
// QCoreApplication object
namespace ToolThreadGlobal
{
static int argc = 1;
static char * argv[] = { "MyVirtualMainApplication.exe", NULL };
static QCoreApplication *coreApp = nullptr;
static ToolThread *toolThread = nullptr;
};
//! The ToolThread class differs from a standard QThread only
//! in its run() method
class ToolThread : public QThread
{
//! Override QThread's run() method so that it calls
//! the QCoreApplication's exec() method rather than
//! the QThread's own
void run() { ToolThreadGlobal::coreApp -> exec(); }
};
class ThreadStarter : public QObject
{
Q_OBJECT
public:
//! Constructor
ThreadStarter()
{
// Set up the tool thread:
if (!ToolThreadGlobal::toolThread)
{
ToolThreadGlobal::toolThread = new ToolThread();
connect(ToolThreadGlobal::toolThread, &ToolThread::started,
this, &ThreadStarter::createApplication, Qt::DirectConnection);
// The thread's 'started' event is emitted after the thread
// is started but before its run() method is invoked. By
// arranging for the createApplication subroutine to be
// called before run(), we ensure that the required
// QCoreApplication object is instantiated *within the
// thread* before ToolThread's customised run() method
// calls the application's exec() command.
ToolThreadGlobal::toolThread->start();
}
}
//! Destructor
~ThreadStarter()
{
// Ensure that the thread and the QCoreApplication are cleanly
// shut down:
ToolThreadGlobal::coreApp -> quit();
delete ToolThreadGlobal::coreApp;
ToolThreadGlobal::coreApp = nullptr;
delete ToolThreadGlobal::toolThread;
ToolThreadGlobal::toolThread = nullptr;
}
//! Function to return a pointer to the actual tool thread:
ToolThread* getThread() const { return ToolThreadGlobal::toolThread; }
private:
//! Create the QCoreApplication that will provide the tool
//! thread's event loop
/*! This subroutine is intended to be called from the tool
thread itself as soon as the thread starts up.
*/
void createApplication()
{
// Start the QCoreApplication event loop, so long as no
// other Qt event loop is already running
if (QCoreApplication::instance() == NULL)
{
ToolThreadGlobal::coreApp = new QCoreApplication(ToolThreadGlobal::argc,
ToolThreadGlobal::argv);
}
}
};
To use this, a subroutine called from the main Java applications’ thread just needs to create a ThreadStarter object which will automatically create a ToolThread with a QCoreApplication running inside it:
itsThreadStarter = new ThreadStarter();
itsToolThread = itsThreadStarter -> getThread();
We can then instantiate a QObject class in the usual way, move it to the thread and call its methods asynchronously using QMetaObject::invokeMethod:
itsWorker = new Worker();
itsWorker -> moveToThread(itsToolThread);
QMetaObject::invokeMethod(itsWorker, “doSomethingInteresting”);
When we’re done, we just delete the ThreadStarter object and everything is cleaned up nicely. Apart from the annoying message saying
WARNING: QApplication was not created in the main() thread
on startup, it seems to meet all my requirements.
Except… (and here, at last, is my question).
Occasionally - and without any pattern that I’ve been able to discern so far - I get an error during the shutdown process. Usually it occurs at the line
delete ToolThreadGlobal::coreApp;
but sometimes at the line
ToolThreadGlobal::coreApp -> exec();
(which of course is executed in the thread’s run() method and doesn’t return until after ToolThreadGlobal::coreApp -> quit(); has been fully executed).
Often the error message is a simple access violation; sometimes it’s a rather more involved:
ASSERT failure in QObjectPrivate::deleteChildren(): "isDeletingChildren already set, did this function recurse?", file ..\qtbase\src\corelib\kernel\qobject.cpp, line 1927
I assume it’s because, once I issue the quit() command to the QCoreApplication, I should be waiting for a little while for it to close down the event loop properly before deleting it - just as one would usually call quit() and then wait() on an ordinary QThread before deleting it. However, QCoreApplication doesn’t seem to have the equivalent of a wait() command, and I can’t implement a QTimer to force a delay because it wouldn’t work once I’ve closed down the event loop with quit(). I’m therefore at a loss what to do. I have an inkling that, as QCoreApplication is a QObject, I could call its deleteLater() method but I can’t see where I should call it from.
Is there an expert out there who understands the ins and outs of QCoreApplication and QThread well enough to suggest a solution to this? Or is there a fundamental flaw in the way that I have designed this?