Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Creating a newable QtScript object from C++ class and extend its functions via prototype



  • Hi,
    i'm trying to wrap my head around QtScript. Right now, i'm trying to get a new-able QtScript object representation of a C++ class hierarchy, which works fine so far, using scriptValueFromQMetaObject<>() and Q_SCRIPT_DECLARE_QMETAOBJECT().

    From that point on, I want to extend this object represention with some additional functions via custom prototypes, similiar to the "Default Prototypes Example":http://doc.qt.digia.com/qt/script-defaultprototypes.html shown at Qt Ref. Doc.

    Last point doesn't work, i can compile but get the message "TypeError: Result of expression 'myFruit.eat' [undefined] is not a function" from my QtScriptEngine (according to the code snippets below).

    To show my setting...

    These is my simplified class hierarchy:
    @class Fruit{}

    class ExoticFruit : public Fruit {}

    class Cumquat : public ExoticFruit {

    public slots:
    void someNativeCppCumquatSlot();

    }

    Q_META_DECLARE(Cumquat*)
    Q_SCRIPT_DECLARE_QMETAOBJECT(Cumquat, QObject*)@

    Now I added a prototype class:
    @
    class EatableProto : public QObject
    {
    Q_OBJECT
    public:
    explicit EatableProto(QObject * parent = 0){};

    public slots:
    void eat(){ qDebug() << "yamm"; };
    }
    @

    this is my simplified scripting setup:
    @
    //ctor of scripting class
    class MyScripter::MyScripter()
    {
    m_engine = new QScriptEngine;

    //has no effect, as far as I can see
    m_eatablePrototype = new EatableProto();
    m_engine->setDefaultPrototype( qMetaTypeId<Cumquat*>(), m_engine->newObject(m_eatablePrototype) );

    // works fine
    QScriptValue scriptCumquat = m_engine->scriptValueFromMetaObject<Cumquat>();
    m_engine->globalObject().setProperty("SmallOrange",scriptCumquat);
    }@

    On QtScripting view, I want to achive something like this:
    @
    //QtScript Code:
    var myFruit = new SmallOrange();
    myFruit.someNativeCppCumquatSlot(); //works

    myFruit.eat(); //protoype slot, fails with error 'is not a function'
    @

    I'm working on the prototype approach because I want to have more ExoticFruits in near future.. lets say
    @
    class Lemon : public ExoticFruit {}
    class PineApple : public ExoticFruit {}
    ...
    @

    which are all eatable... but I want them to have eatable in scripting-world only.

    Can someone see my mistake? Any suggestions?
    JSchmidt



  • Okay, i could fix it by my own. My mistake was the usage of the out-of-box macro Q_SCRIPT_DECLARE_QMETAOBJECT() (Qt 4.8.1)

    There is nothing wrong with it, despite the fact, that behind this macro, there is a boiler plate constructor for script objects which does not set the default prototype! Maybe you could see this as a bug? I don't know.

    But more important, to use the defaultPrototype feature in the way I used it above, either you have to define your own constructor for script objects or, a more elegant way, define your own Q_SCRIPT_DECLARE_QMETAOBJECT() macro. Below there is my suggestion with defaultPrototype-Support:

    original Q_SCRIPT_DECLARE_QMETAOBJECT() macro:
    @
    #define Q_SCRIPT_DECLARE_QMETAOBJECT(T, _Arg1)
    template<> inline QScriptValue qscriptQMetaObjectConstructor<T>(QScriptContext *ctx, QScriptEngine *eng, T )
    {
    _Arg1 arg1 = qscriptvalue_cast<_Arg1> (ctx->argument(0));
    T
    t = new T(arg1);
    if (ctx->isCalledAsConstructor())
    return eng->newQObject(ctx->thisObject(), t, QScriptEngine::AutoOwnership);
    QScriptValue o = eng->newQObject(t, QScriptEngine::AutoOwnership);
    o.setPrototype(ctx->callee().property(QString::fromLatin1("prototype")));
    return o;
    }
    @

    improved suggestion with defaultPrototype-support:
    @
    #define Q_SCRIPT_DECLARE_QMETAOBJECT_DEFAULT_PROTOTYPE(T, _Arg1)
    template<> inline QScriptValue qscriptQMetaObjectConstructor<T>(QScriptContext *ctx, QScriptEngine eng, T )
    {
    _Arg1 arg1 = qscriptvalue_cast<_Arg1> (ctx->argument(0));
    T
    t = new T(arg1);
    if (ctx->isCalledAsConstructor()) {
    QScriptValue proto = eng->defaultPrototype(qMetaTypeId<T
    >());
    QScriptValue u = eng->newQObject(ctx->thisObject(), t, QScriptEngine::AutoOwnership);
    u.setPrototype(proto);
    return u;
    }
    QScriptValue o = eng->newQObject(t, QScriptEngine::AutoOwnership);
    o.setPrototype(ctx->callee().property(QString::fromLatin1("prototype")));
    return o;
    }
    @

    All needed voodoo is to ask the ScriptEngine for the defaultPrototype using the template parameter T (line 7) and setting it as prototype of the newly created script object (line 9)

    Maybe it is useful for someone out there!


Log in to reply