How to build an API with QJSEngine?

2019-04-16 00:14发布

问题:

I am starting with Qt and one of my projects is using QJSEngine to evaluate javascript and I want to provide an entire API to the script, with classes and global functions.

Right now my program provides only the ECMAScript default stuff (eval, encodeURI, parseInt, etc...), but I need to expose some custom classes to the code, like the browsers API (WebSocket class, Image class, document object). For example:

var obj = new CustomClass("", 0);
var ret = obj.customClassMethod("[...]!");
customFunction(ret);

I need to define the behavior of the classes in C++, it wouldn't help evaluate the classes definition and let the user code run.

回答1:

In contrast to QScriptEngine, where you can add custom classes if they inherit from QObject by using the Q_SCRIPT_DECLARE_QMETAOBJECT macro, the QJSEngine does not directly provide this functionality.

You can still use the Qt Meta-object system to provide interfaces for Javascript, but you have to instantiate the object in C++ and add it to the Javascript context. Then it's slots, methods defined with Q_INVOKABLE, and properties defined with Q_PROPERTY are all accessible from within the Javascript runtime.

Now you can create a factory which creates instances of your custom class CustomClass for a given QJSEngine wrapped as Javascript objects:

class CustomClassFactory : public QObject
{
    Q_OBJECT
public:
  CustomClassFactory(QJSEngine* engine) : m_engine(engine) {}
  Q_INVOKABLE QJSValue createInstance() {
      // The engine takes ownership and destroys the object if no longer required.
      return m_engine->newQObject(new CustomClass());
  }
private:
    QJSEngine* m_engine;
}

A factory instance needs to be constructed and added to the global object of the Javascript runtime:

QJSEngine engine;
QJSValue factoryObj = engine.newQObject(new CustomClassFactory());
engine.globalObject().setProperty("_customClassFactory", factoryObj);

Now we can construct objects in Javascript with:

var obj = _customClassFactory.createInstance()

As we've come this far, lets additionally inject a constructor for the custom class into the Javascript runtime:

QJSEngine engine;
// Again, the QJSEngine will take ownership of the created object.
QJSValue factoryObj = engine.newQObject(new CustomClassFactory());
engine.globalObject().setProperty("_customClassFactory", factoryObj);
engine.evaluate(
    "function CustomClass() {"
    "    return _customClassFactory.createInstance()"
    "}");

Et voilà, now you can construct C++ object in Javascript, like you would custom Javascript classes:

var obj = new CustomClass()

For the mentioned WebSocket API you could wrap QtWebSocket for that purpose – that was exactly what I required when I came up with the proposed approach.

Note that for the sake of simplicity I omitted parameters for the constructor, but they can simply be added as well.

PS: I would have added more links to the official documentation, but due to the lack of reputation I'm not allowed to.



回答2:

If you look up Documentation of QScriptEngine, or by searching "QScriptEngine examples" you can find some stuff about making Custom C++ Classes available to QScriptEngine.

Here is a good place to start: link to example

QScriptEngine is very similiar to QJsEngine, so it shouldn't be a big problem for you.

Hope this helps :)