[Interest] QApplication event loop advancing with longjmp()
Nikos Chantziaras
realnc at gmail.com
Wed Sep 26 22:23:09 CEST 2012
For the last couple of years, I'm being bothered by an event loop
integration problem. I have some code that runs its own main loop on
program start, and events need to be handled through callbacks. For
example, if the code needs a line of text from the user, it calls:
int getInput(char* buf, size_t buflen);
getInput() is what needs to be implemented in a platform-specific way.
(In this case, Qt-specific.)
It's been more or less working OK for me for a long time, by simply calling:
qApp->sendPostedEvents(0, QEvent::DeferredDelete);
qApp->sendPostedEvents();
qApp->processEvents(QEventLoop::WaitForMoreEvents
| QEventLoop::AllEvents);
in the getInput() function above while waiting for the user to complete
his input. There's a dozen of such callbacks, and in every one of them
I do the manual event loop advancing. Except on Mac OS X, where
QEvent::DeferredDelete hogs the CPU, so no DeferredDelete there. Which
I'm sure can result in nasty issues.
Thiago Macieira once told me that this isn't really a good thing to do.
"It's a hack; don't do it". Amazingly, most Qt front-ends of a
similar nature to mine (meaning applications that implement a GUI for
stuff like emulators, interpreters, virtual machines, etc.) actually do
the same thing. That's because the upstream developers of the core code
don't want to spend a huge amount of work to refactor their (often quite
big) codebases and make their main loop asynchronous. And even if they
did, that would be a huge bother for the people doing the Mac OS X and
MS Windows ports of that software. And that's understandable; after
all, other GUI APIs (win32, Cocoa, Gtk) don't have a problem with this.
If Qt has a problem with it, then the usual consensus is "that's Qt's
problem to solve."
Thiago suggested I use a thread for this; run the external main loop
inside a QThread. That sounds reasonable at first glance, but actually
it doesn't work. QThread also needs its event loop to run in order to
process events. So that wouldn't solve the problem, but instead just
shift it from QApplication to QThread, and we're back at the beginning
(I would have to manually advance the event loop of the QThread this time.)
Then I tried integrating both loops by using coroutines. Yeah, sounds
like a hack already. Basically, what I tried is doing a longjmp() to
the function that calls the blocking main loop, return from there so
that Qt's event loop runs naturally, and then schedule that function
again for execution (with QMetaObject::invokeMethod() and using
Qt::QueuedConnection). Once it's called again, longjmp() from there
back-in inside the blocking main loop so that it continues running:
static jmp_buf jmpToLibLoopCtx;
static jmp_buf jmpToQtCtx;
static bool hasPendingJmp = false;
int main(int argc, char* argv[])
{
//...
MyApplication* app = new MyApplication(argc, argv);
QMetaObject::invokeMethod(app, "entry", Qt::QueuedConnection);
return app->exec();
}
void MyApplication::entry()
{
if (hasPendingJmp) {
hasPendingJmp = false;
longjmp(jmpToLibLoopCtx, 1);
}
if (setjmp(jmpToQtCtx)) {
// Return to Qt's event loop; we're the outermost
// function blocking it.
return;
}
blockForever(); // Start the external main loop.
}
Then, inside the input callbacks like getInput():
if (not setjmp(jmpToLibLoopCtx)) {
// Jump to the Qt event loop so we can process
// input events.
hasPendingJmp = true;
longjmp(jmpToQtCtx, 1);
}
// If we get here, we have our input ready waiting for
// us in a buffer.
And finally, when the the blocking main loop is ready to be resume, for
example because the user finished her input, then keyPressEvent()
schedules entry() for execution:
QMetaObject::invokeMethod(app, "entry", Qt::QueuedConnection);
But it doesn't work; after longjmp'ing from entry() back inside the main
loop, all data is now garbage, including the "this" pointer, and the
application segfaults. Maybe when entry() returns, the stack frame is
being invalidated, so that longjmp()'ing later back to the blocking main
loop gets you in a non-existent stack frame. Or maybe Qt is simply not
compatible with longjmp(). Or I'm doing it wrong :-) (And hopefully
*that's* the problem here.)
In any event, I would like to ask if anyone else here ever successfully
did something like that; happily combining a blocking external main loop
with Qt's event loop without manually using processEvents(), or has any
enlightening ideas on how this could be done.
More information about the Interest
mailing list