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