[Qt-interest] QtScript, custom QObject subclasses, and hasOwnProperties()

Nathan Carter nathancarter5 at gmail.com
Mon Feb 16 16:00:09 CET 2009


Since my previous post didn't go over so well (i.e., no responses :) )  
I'll rephrase:

When I incorporate QObjects into script land in the method recommended  
by Qt's documentation, I get objects with the C++-land methods and  
properties exposed directly in the object, not factored out to a  
common prototype object.  Is this the intended behavior or am I doing  
something wrong?

If it is the intended behavior, then how do I achieve my desired  
behavior of putting all methods in the prototype object?  Do I have to  
create a custom C++ class for the prototype object that just forwards  
all method calls to inside the thisObject?  That seems like  
reinventing the wheel, and that there must be a better way.

Nathan


On Feb 12, 2009, at 2:31 PM, Nathan Carter wrote:

>
> Dear list,
>
> When I follow the recommended procedures in the docs for getting my  
> custom QObject subclass accessible from script, it works fine.   
> However, I notice that all the functions and properties exposed in  
> script land by such objects are not at the prototype level, but at  
> the individual instance level.  This is despite the fact that I've  
> done what the docs suggest for creating a prototype object.
>
> I've created a small compilable example using a dummy QObject  
> subclass called Foo.  I include that example in this email message;  
> it is three source files and a .pro file.  Its output is below,  
> slightly edited with explanations, followed by the files  
> themselves.  The part of the output to which my question pertains is  
> marked with *******s, about half way down.
>
> My apologies for the enormous email, but I tried to make this post  
> as useful/information-ful as possible.
>
> Thanks for any light users/trolls can shed.
>
> Nathan
>
>
>
> We can construct Foo objects with Foo() calls
> ----------------------------------------------------------
> Script code: Foo()
> Result:      Foo object with data ""
> Script code: Foo('bing')
> Result:      Foo object with data "bing"
> Script code: new Foo('hmm')
> Result:      Foo object with data "hmm"
>
> It works the same if we use new Foo() calls
> ----------------------------------------------------------
> Script code: Foo() instanceof Foo
> Result:      true
> Script code: Foo('bing') instanceof Foo
> Result:      true
> Script code: new Foo('hmm') instanceof Foo
> Result:      true
>
> Foo objects work okay in script
> ----------------------------------------------------------
> Script code: tmp = Foo()
> Result:      Foo object with data ""
> Script code: tmp.get()
> Result:
> Script code: tmp.set('abc')
> Result:      undefined
> Script code: tmp.get()
> Result:      abc
>
> Foo objects have "own" keys that should be in prototype
> ----------------------------------------------------------
> Script code: keys( {} )
> Result:
> Script code: keys( Foo.prototype )
> Result:       
> objectName 
> ,destroyed 
> (QObject*),destroyed(),deleteLater(),set(QString),get(),toString()
> Script code: keys( Foo() )
> Result:       
> objectName 
> ,destroyed 
> (QObject*),destroyed(),deleteLater(),set(QString),get(),toString()
> Script code: keys( Foo('bing') )
> Result:       
> objectName 
> ,destroyed 
> (QObject*),destroyed(),deleteLater(),set(QString),get(),toString()
> Script code: keys( new Foo('hmm') )
> Result:       
> objectName 
> ,destroyed 
> (QObject*),destroyed(),deleteLater(),set(QString),get(),toString()
> **************************
> 	keys() is a function that lists just those strings for which
> 	the argument returns true when asked .hasOwnProperty(thatKey).
> 	(See code in main.cpp, below.)
> 	I would expect the above tests to list lots of stuff in  
> Foo.prototype,
> 	but not list lots of stuff in Foo() or Foo('bing') or new Foo('hmm'),
> 	because they can simply refer to the prototype.
> 	This causes problems with a nontrivial class (unlike Foo) with lots  
> of
> 	properties and methods, because routines that process all of them
> 	(especially in a recursive object hierarchy) slow down a lot, and
> 	unnecessarily.
>
> 	The following tests were to see if QtScript used the methods in the  
> prototype object
> 	in any very clever way, but it doesn't seem so.  It's just another  
> object, and its
> 	methods only refer to itself ("this" does not get swapped in/out).
> **************************
>
> An experiment...
> ----------------------------------------------------------
> Script code: A = new Object()
> Result:      [object Object]
> Script code: B = new Object()
> Result:      [object Object]
> Script code: A.__proto__ = Foo.prototype
> Result:      Foo object with data ""
> Script code: B.__proto__ = Foo.prototype
> Result:      Foo object with data ""
> Script code: A.get()
> Result:
> Script code: B.get()
> Result:
> Script code: A.set('test1')
> Result:      undefined
> Script code: B.set('test2')
> Result:      undefined
> Script code: A.get()
> Result:      test2
> Script code: B.get()
> Result:      test2
>
>
> ===========================================source code  
> follows=========================
>
> 		script_proto_test.pro:
>
> QT += script
> macx:CONFIG -= app_bundle
> win32:CONFIG += CONSOLE
> TEMPLATE = app
> TARGET =
> DEPENDPATH += .
> INCLUDEPATH += .
>
> HEADERS += foo.h
> SOURCES += foo.cpp main.cpp
>
> 		foo.h:
>
>
> #ifndef FOO
> #define FOO
>
>
> #include <QObject>
> #include <QVariant>
>
>
> class Foo : public QObject
> {
>    Q_OBJECT
>
> public:
>
>    Foo ( QString data = QString() );
>    Foo ( const Foo& foo );
>
>    Foo& operator= ( const Foo& other );
>
> public slots:
>
>    void set ( QString data );
>    QString get () const;
>    QString toString () const;
>
> private:
>
>    QString myData;
>
> };
>
>
> Q_DECLARE_METATYPE( Foo )
>
>
> #endif // FOO
>
> 		foo.cpp:
>
>
> #include "foo.h"
>
>
> Foo::Foo ( QString data )
>    : QObject(), myData( data )
> {
> }
>
> Foo::Foo ( const Foo& foo )
>    : QObject()
> {
>    myData = foo.get();
> }
>
> Foo& Foo::operator= ( const Foo& other )
> {
>    set( other.get() );
>    return *this;
> }
>
> void Foo::set ( QString data )
> {
>    myData = data;
> }
>
> QString Foo::get () const
> {
>    return myData;
> }
>
> QString Foo::toString () const
> {
>    return "Foo object with data \"" + myData + "\"";
> }
>
> 		main.cpp:
>
>
> #include <QScriptEngine>
> #include <QDebug>
> #include "foo.h"
>
>
> QScriptValue FooToScriptValue ( QScriptEngine* engine, const Foo& in )
> {
>    Foo* foo = new Foo( in.get() );
>    return engine->newQObject( foo, QScriptEngine::ScriptOwnership );
> }
>
> void FooFromScriptValue ( const QScriptValue& object, Foo& out )
> {
>    Foo* ptr = qobject_cast<Foo*>( object.toQObject() );
>    out = ptr ? *ptr : Foo();
> }
>
> QScriptValue FooScriptConstructor ( QScriptContext* context,  
> QScriptEngine* engine )
> {
>    if ( context->argumentCount() > 1 )
>        return context->throwError( "Too many arguments to  
> constructor" );
>    QScriptValue object;
>    Foo* result = new Foo;
>    if ( context->isCalledAsConstructor() ) {
>        // they've already built it for us, so start by promoting  
> that to a QObject w/new Foo:
>        object = engine->newQObject( context->thisObject(), result,
>                                     QScriptEngine::ScriptOwnership );
>    } else {
>        // not called as "new Foo()", just "Foo()", so create our own  
> new Foo:
>        object = engine->newQObject( result,  
> QScriptEngine::ScriptOwnership );
>        object.setPrototype( engine- 
> >defaultPrototype( qMetaTypeId<Foo>() ) );
>    }
>    if ( context->argumentCount() == 1 )
>        result->set( context->argument( 0 ).toString() );
>    return context->isCalledAsConstructor() ? engine- 
> >undefinedValue() : object;
> }
>
> int main ( int /*argc*/, char** /*argv*/ )
> {
>    QScriptEngine e;
>
>    int FooTypeID = qRegisterMetaType<Foo>( "Foo" );
>    qScriptRegisterMetaType( &e, FooToScriptValue,  
> FooFromScriptValue );
>
>    QScriptValue prototype = e.newQObject( new Foo,  
> QScriptEngine::ScriptOwnership );
>    e.setDefaultPrototype( qMetaTypeId<Foo>(), prototype );
>    QScriptValue constructor = e.newFunction( FooScriptConstructor,  
> prototype );
>    e.globalObject().setProperty( "Foo", constructor );
>
>    e.evaluate( "function test ( expr ) {\n"
>                "    print( 'Script code: ' + expr + '\\nResult:       
> ' + eval( expr ) );\n"
>                "}\n" );
>    e.evaluate( "function keys ( obj ) {\n"
>                "    var result = [];\n"
>                "    for ( var key in obj )\n"
>                "        if ( obj.hasOwnProperty( key ) )\n"
>                "            result.push( key );\n"
>                "    return result;\n"
>                "}\n" );
>
>    qDebug() << "";
>    qDebug() << "We can construct Foo objects with Foo() calls";
>    qDebug() <<  
> "----------------------------------------------------------";
>    e.evaluate( "test( 'Foo()' )" );
>    e.evaluate( "test( 'Foo(\\'bing\\')' )" );
>    e.evaluate( "test( 'new Foo(\\'hmm\\')' )" );
>    qDebug() << "";
>    qDebug() << "It works the same if we use new Foo() calls";
>    qDebug() <<  
> "----------------------------------------------------------";
>    e.evaluate( "test( 'Foo() instanceof Foo' )" );
>    e.evaluate( "test( 'Foo(\\'bing\\') instanceof Foo' )" );
>    e.evaluate( "test( 'new Foo(\\'hmm\\') instanceof Foo' )" );
>    qDebug() << "";
>    qDebug() << "Foo objects work okay in script";
>    qDebug() <<  
> "----------------------------------------------------------";
>    e.evaluate( "test( 'tmp = Foo()' )" );
>    e.evaluate( "test( 'tmp.get()' )" );
>    e.evaluate( "test( 'tmp.set(\\'abc\\')' )" );
>    e.evaluate( "test( 'tmp.get()' )" );
>    qDebug() << "";
>    qDebug() << "Foo objects have \"own\" keys that should be in  
> prototype";
>    qDebug() <<  
> "----------------------------------------------------------";
>    e.evaluate( "test( 'keys( {} )' )" );
>    e.evaluate( "test( 'keys( Foo.prototype )' )" );
>    e.evaluate( "test( 'keys( Foo() )' )" );
>    e.evaluate( "test( 'keys( Foo(\\'bing\\') )' )" );
>    e.evaluate( "test( 'keys( new Foo(\\'hmm\\') )' )" );
>    qDebug() << "";
>    qDebug() << "An experiment...";
>    qDebug() <<  
> "----------------------------------------------------------";
>    e.evaluate( "test( 'A = new Object()' )" );
>    e.evaluate( "test( 'B = new Object()' )" );
>    e.evaluate( "test( 'A.__proto__ = Foo.prototype' )" );
>    e.evaluate( "test( 'B.__proto__ = Foo.prototype' )" );
>    e.evaluate( "test( 'A.get()' )" );
>    e.evaluate( "test( 'B.get()' )" );
>    e.evaluate( "test( 'A.set(\\'test1\\')' )" );
>    e.evaluate( "test( 'B.set(\\'test2\\')' )" );
>    e.evaluate( "test( 'A.get()' )" );
>    e.evaluate( "test( 'B.get()' )" );
>    qDebug() << "";
>
>    return 0;
> }
>
>




More information about the Qt-interest-old mailing list