Importing an existing QScriptExtensionPlugin to QML
-
This topic was touched previously in another thread ( http://developer.qt.nokia.com/forums/viewthread/2378 ), but I find the solution unsatisfactory.
The problem is I have a rather big QScriptExtensionPlugin whose functionality I want to use in QML programs. QDeclarativeEngine internally uses a QScriptEngine for JavaScript evaluation, but it is not exposed. Why? Why cannot I use my existing QtScript extensions in QML programs?
Technically this would be simple, I believe:
@
QScriptEngine* QDeclarativeEngine::scriptEngine() const
{
Q_D(const QDeclarativeEngine);
return &d->scriptEngine;
}
@ -
Hi,
You are right, technically this would be quite simple, and we are hoping to improve this integration in the future (most likely in Qt Quick 2.0). The reason it hasn't yet been done is we aren't 100% sure of the best way to solve this. The easiest approach (basically what you have above) also has some downsides -- for example, QML uses a custom read-only global object for performance reasons, and it would currently be easy to accidentally replace this with raw QScriptEngine access.
For reference, you can follow/comment http://bugreports.qt.nokia.com/browse/QTBUG-11942 for this task.
Regards,
Michael -
I desperately needed this feature, so I hacked up an ugly but working solution that doesn't need modifications to Qt sources. I'm posting it in case someone will find it useful.
First, this function hacks out the address of the private QScriptEngine in QDeclarativeEngine. I haven't tested it with anything else than Linux and GCC on an x86-64.
@
QScriptEngine* findScriptEngine(QDeclarativeEngine* engine)
{
struct FakeScriptEngineData
{
// we know this address (QScriptEngine::rootContext())
void *rootContext;
bool isDebugging;bool outputWarningsToStdErr; void *contextClass; void *sharedContext; void *sharedScope; void *objectClass; void *valueTypeClass; void *typeNameClass; void *listClass; void *globalClass; void *cleanup; void *erroredBindings; int inProgressCreations; QScriptEngine scriptEngine;
};
// skip vtable and assume each address in the structure is aligned
// to sizeof(void*)
void** pPrivateData = reinterpret_cast<void***>(engine)[1];
void* pRootContext = engine->rootContext();
for (int i=8; i<32; ++i)
if (pPrivateData[i] == pRootContext)
{
FakeScriptEngineData* pData = reinterpret_cast<FakeScriptEngineData*>(pPrivateData + i);
return &pData->scriptEngine;
}
return 0;
}
@To import my custom extension I'm doing this:
@
void importExtension(QDeclarativeEngine* engine)
{
QScriptEngine* pEngine = findScriptEngine(engine);
QScriptClass* pOldClass = pEngine->globalObject().scriptClass();
// This makes the read-only global object temporarily read-write
pEngine->globalObject().setScriptClass(0);QScriptValue result = pEngine->importExtension("Into");
if (pEngine->hasUncaughtException())
qWarning("%s", qPrintable(result.toString()));// Make the object read-only again
pEngine->globalObject().setScriptClass(pOldClass);
}
@My tests went fine, and I can use my JS extensions in QML. But I'm a bit afraid this may cause problems later. Why is the global object read-only in the first place? Am I breaking something by making it temporarily read-write?
-
Thanks for this, also works in windows 7 w/mingw32. I automagically generate many toScriptValue/fromScriptValue definitions to define my OmniORB IDL structures for use in the QScriptEngine. I had to use this trick to access them from QML. Hopefully there will be easier access to the script engine or atleast duplicate the to/from semantics to generate objects easier than QDeclarative wrappers.
-
In QtQuick2.0 we have Module APIs - which can be either QObject module APIs, or QJSValue module APIs. It might not be applicable directly for your situation (where you're obviously integrating tightly with QtScript) but QJSValue module APIs should allow clients to provide arbitrary JS data / etc for use in QML. There are some limitations, however, so I guess it depends on what QJSValue allows.
-
I'd like to second a request for a QScriptClass type of interface for QML. I'm looking through the current Qt 5 code and I don't see how QJSValue is going to be comparable at all.
This sort of dynamic binding is trivial in V8, I hope to see some kind of pass through for V8 Interceptors in Qt 5.0.
I must say, it's somewhat surprising this isn't a core feature in QML already.
-
Now that I'm finally porting our Qt4 code to Qt5 I need to solve this issue somehow.
to QJS[X] seems to be mostly straightforward stuff. Still, solution to the original problem with integrating my custom extensions to QML applications isn't obvious. Could someone elaborate the stuff a bit?I don't believe our need is unique in any way: we just need to be able to use our JavaScript extensions from both JavaScript and QML. I'm really surprised how difficult it really is to extend JavaScript this way in Qt5. It may be due to the lack of documentation, and I may certainly have missed something, but this is how I see it:
QtScript seems to be phased out, and it isn't compatible with QML anyway.
There is no documented extension mechanism for QJSEngine.
There is an extension mechanism for QML, but the JavaScript global object is read-only.
The simple question is: how do I add my own JavaScript extensions to QML applications?
This is already a bit out of topic, but I have to admit I'm bit lost regarding the integration of C++ and QML code. I cannot see QJSValue appear anywhere in QML documentation. Does this mean QML has its own set of reflection objects to the C++ side? Such as QQmlProperty?
Let's assume I have this QML code:
@
// main.qml
import QtQml 2.0QtObject
{
id: testfunction test() {}
}
@Assume I have created an object out of the "test" component:
@
QQmlEngine engine;
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
QObject* test = component.create();
@Now I have a QObject pointer in C++.
How do I iterate the properties and functions the QML object has?
How do I add another function, say test2() to the object?
What about adding properties?
How do I call test()?