Note
Japanese Version is here: QtScript
Note
This page contains the contents that I researched in work time. This page is sponsered by DeNA Co.,Ltd.
Qt has QtScript.
Note
I am using 4.7.4 on Mac.
QtScript Engine is implemented as Qt class. You can use several features from C++.
Using QtScript
To build the application that using QtScript, you should add lines about QtScript to your project file.
QT += \
script \
scripttools
First line script is QtScript, and Second line scripttools is QtScript Debugger. Next, add #include statement to your source code, like main.cpp.
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#ifndef QT_NO_SCRIPTTOOLS
#include <QtScriptTools/QScriptEngineDebugger>
#endif
After that, you can instantiate QtScript Engine.
QScriptEngine engine;
QScriptValue ret = engine.evaluate("return Math.pow(2, 10)");
qDebug() << ret.toInteger();
You can attach the debugger only adding three lines:
QScriptEngineDebugger* debugger = new QScriptEngineDebugger();
debugger->attachTo(&engine);
debugger->action(QScriptEngineDebugger::InterruptAction)->trigger();
You can launch the debugger from script by using debugger; statement. To realize that, you should write first two lines of above code sample.
You can read external JavaScript source files by using Qt’s file API.
QString fileName = "file path of your file.js";
QFile scriptFile(fileName);
scriptFile.open(QIODevice::ReadOnly);
QTextStream stream(&scriptFile);
QString contents = stream.readAll();
scriptFile.close();
engine.evaluate(contents, fileName);
If you want to deploy your JavaScript files, you should add these files to your project file(.pro).
folder_01.source = js
folder_01.target = .
DEPLOYMENTFOLDERS = folder_01
On MacOSX, these files are copied to Contents/Resources/js in .app file. I have never tried other environment, but from other sample code, on Windows, these files are copied to the same folder of the executable file. On *nix OS, if the application is installed to /usr/local/bin, other resource files will be installed to /usr/loca/share/*application name*/ folder.
QString adjustPath(const QString &path)
{
#ifdef Q_OS_UNIX
#ifdef Q_OS_MAC
if (!QDir::isAbsolutePath(path))
return QCoreApplication::applicationDirPath()
+ QLatin1String("/../Resources/") + path;
#else
const QString pathInShareDir = QCoreApplication::applicationDirPath()
+ QLatin1String("/../share/")
+ QFileInfo(QCoreApplication::applicationFilePath()).fileName()
+ QLatin1Char('/') + path;
if (QFileInfo(pathInShareDir).exists())
return pathInShareDir;
#endif
#endif
return path;
}
This function convert your deployed resource files’ path to actual path on your environment. If you want to use main.js, use this function like this:
QString fileName = adjustPath("main.js");
Let’s implement console.log. Following methods are much used for adding element into QtScript Engine.
At first, creating function that is called from JavaScript in the C++ source file.
static QScriptValue consoleLogForJS(QScriptContext* context, QScriptEngine* engine)
{
QStringList list;
for(int i=0; i<context->argumentCount(); ++i)
{
QScriptValue param(context->argument(i));
list.append(param.toString());
}
qDebug() << list.join(" ");
return engine->undefinedValue();
}
You can get JS parameter from context. This function joins input parameters and passes to qDebug.
Let’s register this function to QScriptEngine.
QScriptEngine engine;
QScriptValue globalObject = engine.globalObject();
QScriptValue console = engine.newObject();
globalObject.setProperty("console", console);
QScriptValue consoleLog = engine.newFunction(consoleLogForJS);
console.setProperty("log", consoleLog);
At first, this code creates console empty object under global name space. Then append above function as log under console object.
I don’t try, but you can add object it inherits QObject easily.
Writing large program on one file is difficult and complex and crazy. Let’s implement require() function, that is defined in common specification of server side JavaScript. It makes enable to support source file dividing.
Our require() function’s spec is following:
At first, let’s create the base C++ function of require(). This function searchs target files from paths array.
The return type of the property() method is QScriptValue. Note this is not a link to internal object but copy. You can’t change value in QtScript Engine before calling setProperty(). So appending new element to converted paths is nonsense. Take care!
static QScriptValue requireForJS(QScriptContext* context, QScriptEngine* engine)
{
QString requiredPath;
bool found = false;
QString param = context->argument(0).toString();
param.push_back(".js");
QStringList paths = engine->globalObject().property("require").property("paths").toVariant().toStringList();
QFileInfo requiredFileInfo(param);
QScriptContextInfo contextInfo(context->parentContext());
QString parentFileName(contextInfo.fileName());
paths.push_front(QFileInfo(parentFileName).dir().path());
foreach(QString includePath, paths)
{
qDebug() << includePath;
QFileInfo includePathInfo(includePath);
if (includePathInfo.exists())
{
QDir includePathDir(includePath);
includePathDir.cd(requiredFileInfo.dir().path());
requiredPath = QDir::cleanPath(includePathDir.absoluteFilePath(requiredFileInfo.fileName()));
if(QFileInfo(requiredPath).exists())
{
qDebug() << requiredPath;
found = true;
break;
}
}
}
if (!found)
{
qDebug() << "require() file not found: " << param;
return engine->undefinedValue();
}
QScriptValue modules = engine->globalObject().property("$MODULES");
QScriptValue existedModule = modules.property(requiredPath);
if (existedModule.isValid())
{
return existedModule;
}
else
QFile requireFile(requiredPath);
requireFile.open(QIODevice::ReadOnly);
QScriptContext* newContext = engine->pushContext();
QScriptValue exportsObject = engine->newObject();
newContext->activationObject().setProperty("exports", exportsObject);
engine->evaluate(requireFile.readAll(), requiredPath);
exportsObject = newContext->activationObject().property("exports");
engine->popContext();
requireFile.close();
modules.setProperty(requiredPath, exportsObject);
return exportsObject;
}
}
Register this function to global name space. If you want to add new place to search path, you can add in this code.
この処理はJavaScriptの配列の操作をする参考にもなると思います。
QScriptEngine engine;
QScriptValue modules = engine.newObject();
globalObject.setProperty("$MODULES", modules);
ScriptValue require = engine.newFunction(requireForJS);
gobalObject.setProperty("require", require);
ScriptValue paths = engine->newArray();
QScriptValue push = paths.property("push");
push.call(paths, QScriptValueList() << QScriptValue("/search/path/you/want/to/add"));
require.setProperty("paths", paths);
Note
I tried 4.7.4 on Mac.
Default QtScript object is just ECMA Script Engine. It doesn’t have any extra objects except embedded object (Array, Math, RegExp). You can’t show Qt UI form by using default QtScript engine. If you want to use Qt objects in QtScript, you have to register from C++.
You can register almost all (not all) Qt object by using QtScript Binding Generator. You can find many pages but the latest one is here. Other’s are old.
My Qt is installed at ~/QtSDK/. Let’s create JS binding for this Qt library. I refered this web site to build binding. You have to set enveironment variables, this is not described at README. Above page is described for Windows environment, but I can build on Mac too.
$ export QTDIR=~/QtSDK/Desktop/Qt/474/gcc/
$ export PATH=$PATH:$QTDIR/bin
Download QtScript Binding Generator. I downloaded from this page, but you can use git too. You get qt-labs-qtscriptgenerator folder.
$ cd qt-labs-qtscriptgenerator/generator
$ qmake
$ make
(wait...)
$ ./generator --include-paths=~/QtSDK/Desktop/Qt/474/gcc/include
Please wait while source files are being generated...
(wait...)
Classes in typesystem: 639
Generated:
- classes...: 609 (607)
- header....: 410 (408)
- impl......: 410 (408)
- modules...: 22 (21)
- pri.......: 11 (11)
Done, 6 warnings (1220 known issues)
If you forget setting environment vaiables, The classes become 7. If you can build successfully, You see the more than 600 here.
$ cd ../qtbindings/
$ qmake
$ make
(しばらく待つ)
It creates binding libraries. At the same time, it creates test JS interpreter that can use Qt classes. You can test the generated bindings:
$ qs_eval/qs_eval ../examples/AnalogClock.js
If you can see this dialog, congratulations!
At first, put QtScript binding on plugins/script folder. And register these files to project file to deploy.
folder_01.source = js
folder_01.target = .
folder_02.source = plugins
folder_02.target = .
DEPLOYMENTFOLDERS = folder_01 folder_02
To load QtScript binding, you call setLibraryPaths of Application object and importExtensions() method of QScriptEngine object. This is little long code, but what to do is simple.
QScriptEngine engine;
QDir pluginDir(QApplication::applicationDirPath());
#ifdef Q_OS_MAC
pluginDir.cdUp();
pluginDir.cd("Resources");
#endif
if (!pluginDir.cd("plugins")) {
fprintf(stderr, "plugins folder does not exist -- did you build the bindings?\n");
return(-1);
}
QStringList paths = app.libraryPaths();
paths << pluginDir.absolutePath();
app.setLibraryPaths(paths);
QStringList extensions;
extensions << "qt.core"
<< "qt.gui"
<< "qt.xml"
<< "qt.svg"
<< "qt.network"
<< "qt.sql"
<< "qt.opengl"
<< "qt.webkit"
<< "qt.xmlpatterns"
<< "qt.uitools";
foreach (const QString &ext, extensions) {
QScriptValue ret = engine.importExtension(ext);
if (ret.isError())
{
qDebug() << "Error occured to load extionsion: " << ret.toVariant() << ext;
}
}
Note
Reference: Lively for Qt: http://lively.cs.tut.fi/qt/installation.html