【问题标题】:QtWebEngine - synchronously execute JavaScript to read function resultQtWebEngine - 同步执行 JavaScript 读取函数结果
【发布时间】:2018-01-01 23:37:41
【问题描述】:

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

    QString get()
    {
        QString result;

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

        return result;
    }

就是执行test()JS函数,返回本次调用的结果。

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

【问题讨论】:

    标签: c++ multithreading qt callback qtwebengine


    【解决方案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】:

      C++ invokes javascript function

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

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

      假设你的网页上已经定义了jsfun,否则,你必须在字符串参数中定义它。返回值作为参数 v 异步传递给 lambda 表达式。

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          【解决方案4】:

          回调是异步的,因为 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-&gt;page()-&gt;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));
          }
          

          【讨论】:

          • 谢谢,它看起来不错,但似乎不起作用。它在 loop.exec(); 中获得堆栈; - 这还没有结束。我将视图更改为指针(而不是引用),但这没关系。
          • 将示例替换为我测试过的示例。
          • 它工作得非常好!非常感谢。我也使用 std mutex 和 cond 完成了这项工作。 var 和阻塞队列。但是我不得不生成新线程,因为实际上,调用 runJavaScript 的线程和执行回调的线程是相同的线程......我使用 std::this_thread::id 检查了这一点 - 这些不是不同的进程(正如你所写) 但完全相同的线程。无论如何,您的解决方案是真正同步的,所以正是我所需要的。干杯!
          • 我没有说回调是在不同的进程中执行的。我说过 JavaScript 代码(即 runJavaScript 的参数)在称为 QWebEngineProcess 的不同进程中执行(实际上是 JIT 编译和执行)。
          猜你喜欢
          • 2016-09-10
          • 1970-01-01
          • 1970-01-01
          • 2011-12-27
          • 2011-11-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多