Communication between C++ and Javascript for WebGL

2019-02-25 02:17发布

问题:

My friend and I are trying to communicate between C++ and Javascript. We want to render some spheres at a given position with WebGL embedded in a C++/Qt (version 5.6) application.

To do that, we use a QWebView which reads a HTML file with all the JS code for WebGL. This works well, we have a great high-level 3D renderer inside our application. The problem is that the position is known by the C++ and has to be shared to the JavaScript.

More precisely, the C++ knows the number of spheres we need to display and their position, and has to tell it to the Javascript side. What is the best way to do that?

Note : We tried to use Qt Canvas3D but it was too slow in comparison to the classic WebGL.

EDIT 1 - After the first try

I tried to do what you wrote and also read some examples (like this one) but I cannot make it work.

I created a subclass of QWebEngineView where I do all my initializations and put my datas.

.h :

#ifndef WEBGLVIEW_H
#define WEBGLVIEW_H

#include <QObject>
#include <QtWebEngineWidgets>
#include <QtWebChannel/QtWebChannel>

class WebGLView : public QWebEngineView
{
    Q_OBJECT
    Q_INVOKABLE int getNumber();
    Q_PROPERTY(int num READ getNumber)
public:
    WebGLView(QWidget *parent = 0);

private  :
    QWebChannel*    m_channel;
};

#endif // WEBGLVIEW_H

.cpp :

#include "WebGLView.h"
#include <QCoreApplication>
#include <QUrl>
#include <QDebug>

WebGLView::WebGLView(QWidget *parent) :
       QWebEngineView(parent)
{
    // Set up the communications channel
    //C++ to Java test
    m_channel = new QWebChannel(this);
    this->page()->setWebChannel(m_channel);
    m_channel->registerObject(QString("test"),this);

    QString path =  QCoreApplication::applicationDirPath() + "/test6.html";
    this->setUrl(QUrl::fromLocalFile(path));
}

int WebGLView::getNumber()
{
    qDebug() << "getNumber";
    return 10;
}

At the beginning of the JS :

<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
...
var JSobject;
if (typeof qt != 'undefined') {new QWebChannel(qt.webChannelTransport, function(channel) {
    JSobject = channel.objects.test;
} );

When I want to access the value :

if (typeof widget !== 'undefined') {
   var nspherefromc =  JSobject.getNumber;
} else {var nspherefromc = 50;}

When I run it :

  • getNumber is called once, and it seems that it comes after a call to QWebChannel
  • I have warning : Property 'num'' of object 'WebGLView' has no notify signal and is not constant, value updates in HTML will be broken!
  • nspherefromc is the number of spheres that will be displayed. 50 are displayed, not 10.

Any ideas ?

EDIT 2 - Second try

I still cannot make it work. I tried to add a new variable named more to see if a function is called or a if statement is entered :

var more = 0;
        if (typeof qt != 'undefined') {
            new QWebChannel(qt.webChannelTransport, function(channel) {
                JSobject = channel.objects.test;
                more = 1000;
            }
            );
        }

I still have :

    if (typeof JSobject !== 'undefined') {
        nspherefromc =  JSobject.num;
    }
    else {nspherefromc = 50;}
    ...
    var spheresNum = nspherefromc + more;//Number of Spheres

When running it, I only have 50 spheres. The function in the QWebChannel is not called. I'm I suppose to call it myself ? And when ? I also tried the more debug in the if(typeof qt != 'undefined') sttement and I got 1050 spheres.

EDIT 3 - With a HTML Debugger

    function init() {
        var JSobject;
        var nspherefromc = 0;
        if (typeof qt !== 'undefined') {
            new QWebChannel(qt.webChannelTransport, function(channel) {
                JSobject = channel.objects.test;
                console.log(JSobject); //-> returns the object, I can access the functions and everything
                nspherefromc =  JSobject.num;
                console.log("in the function " + nspherefromc); //prints 5000, what i sent from C++
                more = 1000;
            }
            );
            console.log(JSobject); // prints undefined
        }
        console.log(JSobject); // prints undefined
        if (typeof JSobject !== 'undefined') {
            console.log("Yeaaaah"); //Not reached
            nspherefromc =  JSobject.num;
            nspherefromc + 1;
        }
        ...
        console.log("when loading " + nspherefromc + " (more is = to " + more); // nsphere = 0 , more = 0
            var spheresNum = nspherefromc + more;

Which means that outside of the function I cannot reach the JSobject anymore. Do you know why ?

EDIT - In the end The openGL is already initialized when the data from the C++ is received. Therefore, the number of spheres displayed is not the one the C++ asked.

回答1:

In Qt5.6, if you want to make C++ part and JavaScript to communicate, the only way to do it is using QWebChannel on a QWebEngineView. You do it this way in the .cpp file:

m_pView = new QWebEngineView(this);
QWebChannel * channel = new QWebChannel(page);
m_pView->page()->setWebChannel(channel);
channel->registerObject(QString("TheNameOfTheObjectUsed"), this);

Here, you just say that you register an object named TheNameOfTheObjectUsed that will be available on the JS side. Now, this is the part of code to use in the JS side :

new QWebChannel(qt.webChannelTransport, function (channel) {
            // now you retrieve your object
            var JSobject = channel.objects.TheNameOfTheObjectUsed;
        });

And in your case, you want to retrieve the number of spheres, some positions, etc. So you need to have a method on the C++ side which returns a string, an integer, a long... This is what it looks like on the C++ side, in your .h:

Q_INVOKABLE int getNumberOfSpheres();
Q_PROPERTY(int NumberOfSpheres READ getNumberOfSpheres);

And now, you get the number of spheres like this on the JS side :

var nbSpheres = JSobject.NumberOfSpheres;

This is a very simple explanation, and I recommend you to watch this video which was very useful to me. Also, you might want to read more about the JavaScript API provided by QWebChannel, as well as the documentation about QWebChannel;

Hope that helps!


After your edit 1

In your .h, you need to add (or change):

int m_pNumber;

Q_PROPERTY(int num READ getNumber WRITE setNumber NOTIFY numChanged)

Q_INVOKABLE void setNumber(int number); // add this function to the cpp file as well

//add a signal for NOTIFY
signals:
    void numChanged();

public slots:
    void numHasChanged();

In your constructor in the .cpp :

connect(this, SIGNAL(numChanged()), this, SLOT(numHasChanged()));

And in the cpp itself :

void WebGLView::setNumber(int number)
{
    m_pNumber = number;
    emit numChanged();
}

int WebGLView::getNumber()
{
    return m_pNumber;
}


void WebGLView::numHasChanged()
{
     // do anything here
}

And very important in the JS side :

First, be careful, you copied some code without checking it, so I think you meant :

if (typeof JSobject !== 'undefined')

instead of

if (typeof widget !== 'undefined')

And then :

var nspherefromc =  JSobject.num; // this is ok
var nspherefromc =  JSobject.getNumber; // this is NOT ok

Now, whenever you'll change your variable num in JS, a signal will be emitted (numChanged) and you will be able to get the value in the corresponding slot with a getNumber().