QtWebEngine - 同步执行JavaScript以读取函数结果

时间:2017-07-26 14:51:07

标签: c++ multithreading qt callback qtwebengine

我的一个C ++类中使用了以下方法(使用QtWebEngine):

    QString get()
    {
        QString result;

        view->page()->runJavaScript("test();", [this](const QVariant &v)
            {
                result = v.toString();
            });

        return result;
    }

执行test() JS函数并返回此调用的结果。

不幸的是,回调是异步的,程序崩溃了。我怎样才能使它发挥作用?

4 个答案:

答案 0 :(得分:6)

回调是异步的,因为JavaScript执行不仅发生在另一个线程中,而且发生在另一个进程中。所以没有办法让它完全同步。

最好的解决方案是将C ++代码迁移到异步工作。如果你不能这样做,唯一可行的解​​决方案是使用QEventLoop,有点像这样:

void ranJavaScript()
{
    emit notifyRanJavaScript();
}

QString get()
{
    QString result;
    QEventLoop loop;
    QObject::connect(this, SIGNAL(notifyRanJavaScript()), &loop, SLOT(quit()));
    view->page()->runJavaScript("test();", [this](const QVariant &v)
        {
            result = v.toString();
            this.ranJavaScript();
        });

    loop.exec();
    return result;
}

但是,请注意,此示例过于简化了实际使用情况:您需要确保在启动事件循环之前未运行JavaScript。最合适的方法是执行一个正确的插槽而不是lambda +将对view->page()->runJavaScript()的调用分解为另一个插槽,该插槽将在启动事件循环后异步调用。对于这样一个看似简单的任务来说,它有很多胶水代码,但这需要它。这是一个例子:

<强> MainWindow.h

#ifndef TMP_MAIN_WINDOW_H
#define TMP_MAIN_WINDOW_H

#include <QMainWindow>
#include <QVariant>

class QWebEngineView;
class QPushButton;

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget * parent = 0);

    QString get();

    void onScriptEnded(const QVariant & data);

Q_SIGNALS:
    void notifyRanJavaScript();

private Q_SLOTS:
    void onButtonPressed();

    void startScript();

private:
    QWebEngineView *    m_view;
    QPushButton *       m_button;
    QString             m_scriptResult;
};

#endif // TMP_MAIN_WINDOW_H

<强> MainWindow.cpp

#include "MainWindow.h"
#include <QWebEngineView>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QEventLoop>
#include <QDebug>
#include <QTimer>

MainWindow::MainWindow(QWidget * parent) :
    QMainWindow(parent)
{
    m_view = new QWebEngineView;
    QWebEnginePage * page = new QWebEnginePage(m_view);
    m_view->setPage(page);

    QString html = QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
                                  "\"http://www.w3.org/TR/html4/strict.dtd\"><html>"
                                  "<head><h3>head</h3>\n</head>"
                                  "<script type=\"text/javascript\">function test() { return \"A!\"; }</script>"
                                  "<body>text\n</body></html>");
    m_view->page()->setHtml(html);

    m_button = new QPushButton;
    m_button->setMinimumWidth(35);
    m_button->setText(QStringLiteral("Test"));
    QObject::connect(m_button, SIGNAL(pressed()), this, SLOT(onButtonPressed()));

    QHBoxLayout * buttonLayout = new QHBoxLayout;
    buttonLayout->addWidget(m_button);
    buttonLayout->addStretch();

    QVBoxLayout * viewLayout = new QVBoxLayout;
    viewLayout->addLayout(buttonLayout);
    viewLayout->addWidget(m_view);

    QWidget * widget = new QWidget(this);
    widget->setLayout(viewLayout);

    setCentralWidget(widget);
}

QString MainWindow::get()
{
    QEventLoop loop;
    QObject::connect(this, SIGNAL(notifyRanJavaScript()), &loop, SLOT(quit()));

    // Schedule the slot to run in 0 seconds but not right now
    QTimer::singleShot(0, this, SLOT(startScript()));

    // The event loop would block until onScriptEnded slot is executed
    loop.exec();

    // If we got here, the script has been executed and the result was saved in m_scriptResult
    return m_scriptResult;
}

void MainWindow::onScriptEnded(const QVariant & data)
{
    qDebug() << QStringLiteral("Script ended: ") << data;
    m_scriptResult = data.toString();
    emit notifyRanJavaScript();
}

void MainWindow::onButtonPressed()
{
    QString str = get();
    QMessageBox::information(this, QStringLiteral("Script result"), str,
                             QMessageBox::StandardButton::Ok);
}

struct Functor
{
    Functor(MainWindow & window) : m_window(window) {}
    void operator()(const QVariant & data)
    {
        m_window.onScriptEnded(data);
    }
    MainWindow & m_window;
};

void MainWindow::startScript()
{
    qDebug() << QStringLiteral("Start script");
    m_view->page()->runJavaScript(QStringLiteral("test();"), Functor(*this));
}

答案 1 :(得分:1)

kchmviewer项目中,我发现了一个非常简单的方法来等待runJavaScript函数的结果。 following code 是继承自 QWebEngineView 的 ViewWindow class 的一部分。

int ViewWindow::getScrollbarPosition()
{
    QAtomicInt value = -1;

    page()->runJavaScript("document.body.scrollTop", [&value](const QVariant &v)
        {
            qDebug( "value retrieved: %d\n", v.toInt());
            value = v.toInt();
        });

    while (value == -1)
    {
        QApplication::processEvents();
    }

    qDebug( "scroll value %d", value.load() );
    return value;
}

答案 2 :(得分:0)

不幸的是,德米特里的解决方案只能部分发挥作用。在他的示例中,按下按钮时调用代码。但是,如果在加载窗口时移动相同的代码以执行,则永远不会调用已完成脚本(onScriptEnded)的插槽。为了解决这个问题,我不得不在另一个QTimer :: singleShot中调用JS本身的评估(在我的项目中称为evaluateJavaScript):

QTimer::singleShot(0, this, [&] { evaluateJavaScript(); });

不幸的是,在我的真实项目中,我有很多电话,通常是一个评估等待另一个评估才能完成。实际上每次都不可能使用它,所以我仍然在寻找解决方案。

答案 3 :(得分:0)

C++ invokes javascript function

与从js调用C ++函数相比,这非常简单。只需使用runJavaScript将函数作为参数传递,如下所示:

view->page()->runJavaScript("jsfun();",[this](const QVariant &v) { qDebug()<<v.toString();});

它假设已经在您的网页上定义了jsfun,否则,您必须在字符串参数中定义它。返回值作为参数v。

异步传递给lambda表达式