1

I'm trying to use QWebPage in a shared library, which means I have to have QApplication in there to get a GUI context for it to run in. I've built my code up to get this in place, however as soon as I run qApp->exec() the Event Loop completely blocks and prevents anything else from executing. This is with the shared library being ran on OS X, I've yet to try any other platforms.

I've tried adding a QTimer in to trigger every 100msecs but that doesn't ever get called, I'd assume to the event loop blocking. I've added my QApplication setup code below. I'd assume I either need to run it in a thread, or I've missed something trivial but I'm completely unsure what.

web_lib.cpp

WebLib::WebLib(int argc, char *argv[])
{
    QApplication a(argc, argv, false);
    connect(&m_eventTimer, SIGNAL(timeout()), this, SLOT(handleEvents()));
    m_eventTimer.start(100);

    a.exec();
}
void WebLib::renderFile(QString file
{
    ...some connection code that's boring here
    m_page = new QWebPage;
    m_page->mainFrame()->load(file);
}
void WebLib::handleEvents() 
{
    qApp->processEvents()
}

web_lib.h

class WEBLIBSHARED_EXPORT WebLib: public QObject
{
    Q_OBJECT
public:
    WebLib();
    WebLib(int argc, char *argv[]);
    void renderFile(QString fileName);

private slots:
    void handleEvents();

private:
    QWebPage *m_page;

    QTimer m_eventTimer;
};

main.cpp

int main(int argc, char *argv[])
{
    WebLib *webLib = new webLib(argc, argv);
    svgLib->renderFileFromName("somePath");

    return 0;
}
Nicholas Smith
  • 11,642
  • 6
  • 37
  • 55
  • What's `this` in your code? Is it a class with the `QTimer` as member variable? – Tarod Feb 02 '16 at 12:31
  • 1
    If you have to run your app in OS X, you won't be able to move `QApplication` to a new thread, as far as I know. It could work on other OS though. You could always reverse it, run GUI library with `QApplication` on the main thread and move the other stuff to a new thread. – thuga Feb 02 '16 at 13:01
  • @Tarod: yeah, `this` is the class containing the timer as a member variable. – Nicholas Smith Feb 02 '16 at 13:03
  • @thuga: I'd ideally like it to be as cross-platform as possible, but working around Cocoas main thread requirement could be a waste of time. – Nicholas Smith Feb 02 '16 at 13:16
  • Can't you just pass a GUI context to the library functions? Trying to instantiate and run an application within the library doesn't make sense to me. – Stuart Fisher Feb 02 '16 at 13:29
  • @StuartFisher: it's likely to be ran from a basic C++ binary, rather than a Qt based one which means it needs to manage itself. It doesn't make much sense to me either, but `QWebPage` cannot be used without it apparently. – Nicholas Smith Feb 02 '16 at 13:32
  • I think thuga is right then, you'd need to move other stuff out of the main gui / qt thread. This doesn't explain why your timer isn't getting called though. – Stuart Fisher Feb 02 '16 at 13:51
  • Your code and your question doesn't indicate what you're trying to accomplish. Yes, you're trying to load a page - what for? Also, the behavior can be reproduced without the library, so you should get rid of that. Always keep minimizing your problem until nothing else can be removed while preserving the "wrong" behavior. The library and the timer are not needed. Your problem is a four liner that I'll reproduce here: `QApplication a(argc, argv); QWebPage page; page.mainFrame()->load("..."); a.exec();`. Your complaint is that `exec` doesn't return. That's your real question, then? – Kuba hasn't forgotten Monica Feb 03 '16 at 14:00

3 Answers3

1

Your event loop has nothing to do. You need to make the render file request before calling a.exec(), not afterwards. In other words, you need to make the following changes:

In the WebLib constructor: 1. Remove the call to a.exec(). 2. Dynamically allocate the QApplication instead of putting it on the stack. 3. Remove the timer, you don't need it.

In web_lib.cpp: Add WebLib::run(), which will call a.exec().

In main.cpp: After the call to renderFile(), call webLib->run().

Stuart Fisher
  • 201
  • 3
  • 8
  • I need to try that properly, however my testing showed that `a.exec()` needed to be called by the end of the constructor otherwise it would segfault on creating the `QWebPage` variable. – Nicholas Smith Feb 02 '16 at 21:42
  • It shouldn't, can you provide more info about the crash? If you can't work around that you could change renderFile so that it posts a custom event which would then make the call to create and load the web page. – Stuart Fisher Feb 03 '16 at 08:37
  • Sure: `EXC_BAD_ACCESS (code=1, address=0x0) frame #0: 0x000000010003081f QtWebKitWidgets WebKit::initializeWebKitWidgets() + 47` If I add the call to `.exec` in it'll allow the initialisation to occur, but then blocks the event loop. – Nicholas Smith Feb 03 '16 at 09:17
  • So I worked around the initialisation fault by making sure that I as initialising the `QWebPage` dynamically, but calling `.exec()` still causes a block, no matter where it's called from. – Nicholas Smith Feb 03 '16 at 12:10
  • I've decided to accept this as the answer as it was pretty much bang on the money, what I do in addition is rather than bringing up an `exec` and starting the event loop, I manually call out to `processEvents` and clear the event queue which solves the problem. – Nicholas Smith Feb 03 '16 at 13:58
  • @NicholasSmith "I manually call out to processEvents and clear the event queue which solves the problem." This is never necessary. You don't provide enough information about what you're trying to do or how or why. I think you're trying to dig yourself much deeper than you need to. The whole thing is an easy thing to do correctly, but please first share what is it that you're trying to do - what is the structure of the entire system? Who consumes your library? The consumer's design is *very important* - give as much detail as you can. – Kuba hasn't forgotten Monica Feb 03 '16 at 14:04
  • It's dumb as a sack of hammers, I'm doing nothing complex. The library takes a file path and loads it into a `QWebPage` instance, once it's done loading I'll render it to a `QImage` object. The library consumer is literally everything in the `main.cpp`. – Nicholas Smith Feb 03 '16 at 14:51
1

As soon as I run qApp->exec() the event loop completely blocks and prevents anything else from executing.

That's correct. After you're done with your rendering, you should exit the event loop.

The timer is useless, since calling processEvents from a nonblocking slot like handleEvents simply forces the event loop to be re-entered for a short time, for no reason.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Timer did seem to be useless, so I've moved on from it. Once I drop into `qApp->exec()` no further code will execute, so how do you suggest I exit the event loop? – Nicholas Smith Feb 03 '16 at 14:54
  • @NicholasSmith It's not true that no further code will execute! That's the point of event loops: they will process the event handlers as events arrive, e.g. due to network activity. If you don't have anything further to do, then you don't even need to start the event loop. If you use network connections, you'll be notified when whatever you wished done, is done, and couple that to `QCoreApplication::quit`. Note that you don't need to use `QCoreApplication::exec`, you can also create an explicit event loop and quit that instead, internally that's what happens. – Kuba hasn't forgotten Monica Feb 03 '16 at 15:37
  • @NicholasSmith Of course your `render` call will block for network access that way, and that might not be what you wish. You could process the network requests only in an event loop in another thread, and offer a different API: one when the user initiates the request, and another where they get called after the network requests are finished and the render can be wrapped up. – Kuba hasn't forgotten Monica Feb 03 '16 at 15:42
  • So for an actual example, I've tried a solution like `connect(page, &QWebPage::loadStarted, m_app, &QApplication::exec) ;connect(page, &QWebPage::loadFinished, m_app, &QApplication::quit)`, which theoretically should quit the event loop when the load finishes, but doesn't. It just sits in the event loop handler when I run a backtrace. – Nicholas Smith Feb 03 '16 at 15:52
  • @NicholasSmith `exec` is not a slot, so the first line won't compile. One "do something when the loop starts" idiom is the zero-duration timer. There's also no guarantee that the `loadStarted` signal will not be emitted from an event handler - thus never if you didn't start the event loop yet. The page might not have finished loading. Do a `connect(page, &QWebPage::loadFinished, []{ qDebug() << "yay, finished!"; })` to confirm that it did. Calling `quit` before `exec` gets called is a no-op. You might wish to make that connection to quit a queued one to work around that. – Kuba hasn't forgotten Monica Feb 03 '16 at 16:25
  • Okay that makes more sense now, but if I'm running `qApp->exec()` after I start my `page->mainFrame()->load()` isn't there the potential for a race condition, or is Qt just adding every event to the queue to be processed when the event loop starts? – Nicholas Smith Feb 03 '16 at 16:57
  • "is Qt just adding every event to the queue to be processed when the event loop starts" That's correct. No races here. – Kuba hasn't forgotten Monica Feb 03 '16 at 16:58
  • This is quite irrelevant and you might already be aware of this, but you say that `exec is not a slot, so the first line won't compile`. Using the new `Qt5` signal and slot syntax, connecting to functions that are not slots is indeed allowed. – thuga Feb 08 '16 at 10:20
  • @thuga Yeah, I missed that. Thanks. – Kuba hasn't forgotten Monica Feb 08 '16 at 15:02
-1

The exec must be run in a tread. Alternatively, you can call QApplication::processEvents periodically.

Velkan
  • 7,067
  • 6
  • 43
  • 87
  • I was unable to call `processEvents` but I'll try running `exec` in it's own thread. – Nicholas Smith Feb 02 '16 at 13:14
  • This is bad advice. Qt doesn't support running the main event loop in any thread other than the main thread. It happens to work on Windows, but is pointless there since the Qt event loop spins a native event loop. On OS X: `QApplication::exec` will spin a native runloop, so you don't need another thread either. Furthermore, OS X doesn't support GUI operations such as window creation etc. from threads other than the main thread, so `qApp->exec()` doesn't make sense in other threads. – Kuba hasn't forgotten Monica Feb 02 '16 at 13:56
  • Your answer implies that `exec` must be run in an *additional dedicated* thread, and that's wrong. Any code that runs will run in some thread. So if you didn't mean an extra thread, then your answer is meaningless: yeah, any method you invoke will run in some thread :) – Kuba hasn't forgotten Monica Feb 02 '16 at 14:21
  • @KubaOber, well, ok, it "occupies the thread entirely". Btw, `qApp->exec()` from other thread makes sense if you need to integrate QGuiApplication into the program that already does GUI through other libraries. Do you have OS X to try running `QGuiApplication::exec` with some non-GUI activity in a separate thread of a non-Qt GUI application? Does it have any side effects? Current QPA system should allow such use case, and it's ok with XCB (as I tested) and Windows (as you've mentioned). – Velkan Feb 02 '16 at 15:23
  • `QApplication::exec` generally cannot run in any thread other than the main thread. If you wish to run a generic event loop, use `QEventLoop`, not `QApplication`. That's all there's to it. – Kuba hasn't forgotten Monica Feb 03 '16 at 13:34
  • @KubaOber, I don't see a fundamental reason for that. And the use case of Qt as a pluggable GUI is perfectly valid. – Velkan Feb 03 '16 at 14:06
  • The fundamental reason is that Qt is not designed nor tested for it. Windows is the only platform where, due to happy coincidences, a non-main-thread `QApplication` "works" - but even there you can run into subtle bugs. If you're making an interactive, gui application, the main thread of your consumer code will eventually run a native event loop. Qt interoperates with that event loop on both Windows and OS X without the need to call `qApp->exec()`. As long as the code you interface with spins the **native** event loop, Qt will be happy. That's by design. – Kuba hasn't forgotten Monica Feb 03 '16 at 14:08
  • @KubaOber, I think it worth using Qt in the manner of how CEGUI or SFGUI are used: do a lightweight integration of the rendering and don't create native windows with Qt (so, inject the input). Do you know someone who does that? – Velkan Feb 03 '16 at 14:45
  • Not creating native windows isn't enough. There's `QPixmap` and a other things you can't use from non-gui threads. Who knows how `QWebPage` and its friends are implemented. It's a deprecated module, but unless it's documented to be safe to use from other threads, you can't use it from other threads - just as you can't use `QWidget` nor `QPixmap`. – Kuba hasn't forgotten Monica Feb 03 '16 at 15:34