Trolltech Home | QSA-interest Home | Recent Threads | All Threads | Author | Date
All threads index page 1

QSA-interest Archive, February 2006
Cannot return custom type from script


Message 1 in thread

Hi,

I tried to create an object of a custom type and want to return it from
the script, so that it ends up as the return value of
QSInterpreter::evaluate(..).

For example:

=====================================
function f()
{
	return new Integer(1);
}

f();
=====================================

Full code is in the attached example. The output is

> --> (double) 1
> --> (QSize) (1,2)
> integer ctor: 0x0973418
> ~integer dtor: 0x0973418
> --> (QObject*) 0x0973418

where one can see that the object to be returned is deleted before it is
wrapped in the QVariant which should return it.

Any idea how I can manage to return an object of a custom type?

/eno

PS: This is qsa 1.2.0 and Qt 4.1.0.



#include "sc.h"
#include "sc.h"

#include <qsinterpreter.h>
#include <qsobjectfactory.h>

QTextStream cerr(stderr);

class factory: public QSObjectFactory {
public:
	factory()
	{ 
		registerClass("Integer", &integer::staticMetaObject); 
	}

	QObject* create(const QString& name, const QVariantList& args, QObject*)
	{
		if(name == "Integer" && args.size() == 1) 
			return new integer(args.at(0).toInt());

		return 0;
	}
};

class interpreter: public QSInterpreter {
public:
	interpreter()
		{ addObjectFactory(new factory()); }

	void evaluate(const QString& code) 
	{
		clear();

		QVariant v = QSInterpreter::evaluate(code);
		cerr << "--> " << v << endl;
	}
};

int main()
{
	interpreter qsa;

	qsa.evaluate("function f() { return 1; } f()");
	qsa.evaluate("function f() { return new Size(1,2); } f()");
	qsa.evaluate("function f() { return new Integer(3); } f()");

	return 0;
}

//
// QTextStream& operator << (QTextStream&, const X&)
//

QTextStream& operator << (QTextStream& o, const QSize& sz)
	{ return o << "(" << sz.width() << "," << sz.height() << ")"; }

QTextStream& operator << (QTextStream& o, const integer& i)
	{ return o << "[int " << i.value() << "]"; }

QTextStream& operator << (QTextStream& o, QObject* p)
{ 
	if(!p) o << "NULL";
	else o << "0x0" << hex << (int)p;

	return o;
}

QTextStream& operator << (QTextStream& o, const QVariant& v)
{
	if(v.canConvert<QSize>()) 
		return o << "(" << v.typeName() << ") " << v.value<QSize>();

	if(v.canConvert<integer>()) 
		return o << "(" << v.typeName() << ") " << v.value<integer>();

	if(v.canConvert<QObject*>()) 
		return o << "(" << v.typeName() << ") " << v.value<QObject*>();

	if(v.typeName())
		return o << "(" << v.typeName() << ") " << v.toString();
	return o << "nil";
}



#include <QObject>
#include <QObject>
#include <QMetaType>

// --- some I/O -------------------------------------------------------

#include <QTextStream>

extern QTextStream cerr;

QTextStream& operator << (QTextStream& o, const QVariant& v);
QTextStream& operator << (QTextStream& o, QObject* p);

class integer: public QObject {
	Q_OBJECT

	Q_PROPERTY(int value READ value WRITE setValue);
	int v;

public:
	integer(int i = 0) 
	{ 
		v = i; 
		cerr << "integer ctor: " << this << endl;
	}

	integer(const integer &i) 
	{
		v = i.value(); 
		cerr << "integer copy ctor: " << this << endl;
	}

	~integer()
	{
		cerr << "~integer dtor: " << this << endl;
	}

	int value() const { return v; }
	void setValue(int i) { v = i; }
};

Q_DECLARE_METATYPE(integer);


TEMPLATE = app
TEMPLATE = app

CONFIG -= release
CONFIG *= debug
CONFIG += qsa
CONFIG += console


HEADERS += sc.h
SOURCES += sc.cpp



Message 2 in thread

You should be able to return it as a QObject*. If it is not based on
QObject you need to create a wrapper factory. Also it might need
registering the object with interpreter->registerMetaObject().

-- 
 [ signature omitted ] 

Message 3 in thread

Seneca wrote:
> You should be able to return it as a QObject*. If it is not based on
> QObject you need to create a wrapper factory. 

I have done this.

> Also it might need
> registering the object with interpreter->registerMetaObject().
> 
what is "interpreter->registerMetaObject()" for? The documentation is
quite sparse here.

The problem seems to be that evaluate() tries to return a QObject*
wrapped in a QVariant, where I would expect an instance of my class
wrapped in a QVariant. That's why I am using
"Q_DECLARE_METATYPE(integer);", right? But even if I take it as a
pointer to an integer wrapped up this pointer is already deleted...

I tried to give all my custom type objects an explicit parent, like this:

class objectfactory: public QSObjectFactory
{
	//
	// This is a real garbage collector. It collects all objects
	// created during the lifetime of this objectfactory. This
	// prevents the otherwise possibly premature deletion by QSA.
	//
	QObject garbage;

	/* ... */
protected:
	QObject* create(const QString& name, const QVariantList& args, QObject*
context)
	{
		QObject* r = create_(name, args, context);
		if(r && !r->parent())
			r->setParent(&garbage);
		return r;
	}
};


This somewhat solves the problem, but then all objects live until the
objectfactory is destroyed.

/eno




To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 4 in thread

So if you want a value back, did you also try

   return Integer(3);

instead of

   return new Integer(3);

(just a wild guess though...)
   
-- 
 [ signature omitted ] 

Message 5 in thread

Seneca wrote:
> So if you want a value back, did you also try
> 
>    return Integer(3);
> 
> instead of
> 
>    return new Integer(3);
> 
> (just a wild guess though...)
>    

In that case QSA produces the following error message:

"Error in script: '', line: 1
  Error. Unable to perform cast to user defined object type 'Integer'"

and the return value is an invalid QVariant.

/eno

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 6 in thread

Are you trying to return a custom script class from a script  
function, and then use it (if even just to pass back to QSA) in C++?   
If so, I don't believe that is possible.  It would be nice if it  
were, though!

Oh, wait, actually I think I see what you're trying to do..  In your  
script function, you create a QSObjectFactory object, and then want  
to return it back to C++, right?  I think the reason it might be  
getting deleted is because in QSA, there is no explicit "delete"  
operator.  Instead, variables are cleaned up when their last  
reference goes out of scope.  Maybe QSA considers the object to be  
out of scope sometime after the return statement but before actually  
returning it, therefore it gets deleted.  Try assigning it to a  
global variable before you return it.

Also, back in the C++ world, it probably will just be a QObject*.   
You can use qobject_cast<> to safely cast it to the correct type.

Joel Nordell
Software Engineer
ONEAC Corp.


On Feb 20, 2006, at 2:16 PM, eno wrote:

> Seneca wrote:
>> So if you want a value back, did you also try
>>
>>    return Integer(3);
>>
>> instead of
>>
>>    return new Integer(3);
>>
>> (just a wild guess though...)
>>
>
> In that case QSA produces the following error message:
>
> "Error in script: '', line: 1
>   Error. Unable to perform cast to user defined object type 'Integer'"
>
> and the return value is an invalid QVariant.
>
> /eno
>
> To unsubscribe - send "unsubscribe" in the subject to qsa-interest- 
> request@xxxxxxxxxxxxx

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 7 in thread

Joel Nordell wrote:
> Are you trying to return a custom script class from a script  function, 
> and then use it (if even just to pass back to QSA) in C++?   If so, I 
> don't believe that is possible.  It would be nice if it  were, though!

This is sort of possible already... If you create your data as an array 
you can pass it back to C++ as a QVariantList, but only your indexed 
data will be available... e.g:

var x = [];
x[0] = "stuff";
x[1] = "other_stuff";
x.foo = "bar";

Where 0 and 1 will be available, but foo will not. If you want the map 
you need to pass the 'x' variable to a slot that takes a QVariantMap as 
parameter.

In QSA 1.2.1 we added some more convenience to this. In which case the 
default conversion to QVariant will look at the type and the above case, 
which has both numeric and string keys will be converted to a 
QVariantMap, making it possible to do:

QVariantMap map = qvariant_cast<QVariantMap>(qsaReturnValue);
map["1"];
map["foo"];
and similar on the C++ side.

Finally...

We also added the option of converting a builtin object to a QVariantMap 
based on the similar rules so if you do:

var x = new Object();
x.foo = '13';
x.bar = '42';
x;

then you can do

map["foo"] ---> 13
map["bar"] ---> 42

on the C++ side.

-

Gunnar

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 8 in thread

eno wrote:

> what is "interpreter->registerMetaObject()" for? The documentation is
> quite sparse here.

It is primarly used for figuring out that you can return a QObject * 
subclass from a slot. In QSA 1.2.1 we'll also use it to type check enums 
used as slot arguments, a bug which was present in QSA 1.2.0.


> The problem seems to be that evaluate() tries to return a QObject*
> wrapped in a QVariant, where I would expect an instance of my class
> wrapped in a QVariant. That's why I am using
> "Q_DECLARE_METATYPE(integer);", right? But even if I take it as a
> pointer to an integer wrapped up this pointer is already deleted...

Objects created by an object factory are deleted unless they have a 
parent if they go out of scope.

I guess you figured out by now that the way to cast a variant to a 
QObject* is using:

QObject *qobj = qvariant_cast<QObject *>(qsaReturnValue);

which doesn't require a metatype declaration, or

integer *i = qvariant_cast<integer *>(qsaReturnValue);

which is available since you metatype declared your type.

> I tried to give all my custom type objects an explicit parent, like this:

...

> This somewhat solves the problem, but then all objects live until the
> objectfactory is destroyed.

What kind of lifetime are you interrested in? That they live longer than 
the objectfactory or that you have more explicit control over them once 
in C++?

-
Gunnar

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 9 in thread

GS> What kind of lifetime are you interrested in? That they live longer than
GS> the objectfactory or that you have more explicit control over them once 
GS> in C++?

The problem is that the last value of the interpreter, which is
returned to the C++ application by evaluate(), is considered as no
more used by the interpreter (a false asumption one could argue).

So in the sample:

QVariant v = evaluate(
    "function f() { return new Integer(3); } f()"
);

the variable v will hold a integer* to a object that was allready
deleted by the interpreter. You could work around by assigning the
value to a global as Joel has allready pointed out wich will inhibit
deletion by the interpreter:

QVariant v = evaluate(
    "function f() { return new Integer(3); } var ret = f()"
);

I wonder if the interpreter could be so smart not to delete the last
active value in future versions of QSA.

-- 
 [ signature omitted ] 

Message 10 in thread

Seneca wrote:
> GS> What kind of lifetime are you interrested in? That they live longer than
> GS> the objectfactory or that you have more explicit control over them once 
> GS> in C++?
> 
> The problem is that the last value of the interpreter, which is
> returned to the C++ application by evaluate(), is considered as no
> more used by the interpreter (a false asumption one could argue).
> 
> So in the sample:
> 
> QVariant v = evaluate(
>     "function f() { return new Integer(3); } f()"
> );
> 
> the variable v will hold a integer* to a object that was allready
> deleted by the interpreter. You could work around by assigning the
> value to a global as Joel has allready pointed out wich will inhibit
> deletion by the interpreter.

Thank your all for your help.

I tried several things meanwhile. At first, I found that the returned
QVariant wraps an QObject*, which turns out to point to my class
implementing QSA's Integers.

But to have a valid object returned (and not an already deleted one,
like Seneca stated) I have to give to parent it outside QSA. Easily
done, now I can do
>
> QVariant v = evaluate(
>     "function f() { return new Integer(3); } var ret = f()"
> );
safely. However, something like that:

> for(var i=0; i<10000; ++i)
> for(var j=0; j<10000; ++j)
>     new Integer(i*j);

would certainly eat up tons of memory now, because all my Integer
objects are still lying around.

> The problem is that the last value of the interpreter, which is
> returned to the C++ application by evaluate(), is considered as no
> more used by the interpreter

...and that should be changed; an object returned by the interpreter
could still be weakly owned (via QPointer) by the interpreter. The
interepreter could easily delete any objects from evaluate() calls
before executing an actual evaluate.

Assign the value to a global var is certainly a workaround. Good to know
that something like that is possible.

/eno

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 11 in thread

> ...and that should be changed; an object returned by the interpreter
> could still be weakly owned (via QPointer) by the interpreter. The
> interepreter could easily delete any objects from evaluate() calls
> before executing an actual evaluate.

What I wanted to say here: if a result object from a prior evaluation
still lies around, before the interpreter is going to evaluate() it
should be deleted (or deleted, if the parent() didn't change).


/eno

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 12 in thread

Hi *,

how can I access a QSA object from C++? I want to have a script like this:

========================================

global.font = new Font();
global.font.family = "Courier"

========================================

In preparation to execute it I would add a "global" transient object to
the interpreter. Now, after evaluating it, is there a C++-ish way to
access global.font from the QSInterpreter instance?

Thanks,
/eno


To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 13 in thread

eno wrote:

>how can I access a QSA object from C++? I want to have a script like this:
>
>========================================
>
>global.font = new Font();
>global.font.family = "Courier"
>
>========================================
>
>In preparation to execute it I would add a "global" transient object to
>the interpreter. Now, after evaluating it, is there a C++-ish way to
>access global.font from the QSInterpreter instance?
>  
>

Hi, Eno.

If "global" is a pointer to your transient object in C++, and "ip" a 
pointer to the interpreter, then you can do as follows:

QFont font = qvariant_cast<QFont>(ip->variable("font", global));

best regards,
Eskil

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx


Message 14 in thread

> Hi, Eno.
> 
> If "global" is a pointer to your transient object in C++, and "ip" a
> pointer to the interpreter, then you can do as follows:
> 
> QFont font = qvariant_cast<QFont>(ip->variable("font", global));
> 
> best regards,
> Eskil

Thanks a lot,

/eno

To unsubscribe - send "unsubscribe" in the subject to qsa-interest-request@xxxxxxxxxxxxx