web-dev-qa-db-ja.com

QtWebEngine-JavaScriptを同期的に実行して関数の結果を読み取る

私のC++クラスの1つに(QtWebEngineを使用して)次のメソッドがあります:

_    QString get()
    {
        QString result;

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

        return result;
    }
_

test() JS関数を実行し、この呼び出しの結果を返します。

残念ながら、コールバックは非同期であり、プログラムはクラッシュします。どうすればそれを機能させることができますか?

10
zupazt3

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が実行されていないことを確認する必要があります。これを行う最も適切な方法は、ラムダの代わりに適切なスロットを実装することです+ view->page()->runJavaScript()の呼び出しを、非同期afterイベントループを開始します。このような一見単​​純なタスクには多くのグルーコードが必要ですが、それが必要です。次に例を示します。

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));
}
_
6
Dmitry

残念ながら、Dmitryのソリューションは部分的にしか機能しません。彼の例では、ボタンを押すとコードが呼び出されます。ただし、ウィンドウの読み込み中に同じコードを実行するように移動した場合、完成したスクリプト(onScriptEnded)のスロットが呼び出されることはありません。これを修正するには、JS自体の評価(私のプロジェクトではevaluateJavaScriptと呼ばれます)の呼び出しを別のQTimer :: singleShot:でラップする必要がありました。

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

残念ながら、私の実際のプロジェクトでは、多くの呼び出しがあり、多くの場合、ある評価が完了する前に別の評価を待っています。これを毎回使用することは事実上不可能なので、私はまだ解決策を探しています。

0
Dimitar Dobrev

C++はjavascript関数を呼び出します

これは、jsからC++関数を呼び出すのに比べてはるかに簡単です。 runJavaScriptを使用して、次のように関数をパラメーターとして渡します。

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

JsfunがWebページですでに定義されていることを前提としています。それ以外の場合は、stringパラメーターで定義する必要があります。戻り値は、パラメーターvとしてラムダ式に非同期的に渡されます。

0
peter