[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