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

Nathan Carter nathancarter5 at gmail.com
Thu Feb 12 20:31:41 CET 2009


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