From 19e3b60133ec98adc1c857eecbd5e15967354914 Mon Sep 17 00:00:00 2001 From: lemmel <> Date: Fri, 16 Feb 2018 13:36:52 +0100 Subject: [PATCH] adding a Qt's wrapper that allows: - to access from ChakraCore to Qt's Object - set variables from C++ - get variables from ChakraCore Furthermore it can be a useful because it offers several examples: - access to variables from ChakraCore - registering attribute's object and be able to get notified when they are changed --- qtwrapper/README.md | 33 ++ qtwrapper/main.cpp | 27 ++ qtwrapper/qtobject.cpp | 10 + qtwrapper/qtobject.h | 21 + qtwrapper/qtwrapper.pro | 34 ++ qtwrapper/qtwrapper/qjsengine.cpp | 612 ++++++++++++++++++++++++++++++ qtwrapper/qtwrapper/qjsengine.h | 35 ++ 7 files changed, 772 insertions(+) create mode 100644 qtwrapper/README.md create mode 100644 qtwrapper/main.cpp create mode 100644 qtwrapper/qtobject.cpp create mode 100644 qtwrapper/qtobject.h create mode 100644 qtwrapper/qtwrapper.pro create mode 100644 qtwrapper/qtwrapper/qjsengine.cpp create mode 100644 qtwrapper/qtwrapper/qjsengine.h diff --git a/qtwrapper/README.md b/qtwrapper/README.md new file mode 100644 index 0000000..d22105a --- /dev/null +++ b/qtwrapper/README.md @@ -0,0 +1,33 @@ +# Qt Wrapper Sample (for Shared Library) +This sample shows how to wrap Qt's objects in order to embed them in ChakraCore. +It shows how to retrieve and set values from C++ world. + +1. Initializing the JavaScript runtime and creating an execution context. +2. Running scripts and handling values. +3. Printing out script results. + +## Requirements +This code was tested with Qt 5.10 and ChakraCore 1.9 (not the stable version, the master, commit 3f7691187ba02ab2a1079619fed867714786b693, at Feb 10 14:50:00 2018 -0800), under Linux. + +This code should run with "any" Qt version as there is no fancy things. + + +## Build and run the sample +1. [Clone](https://github.com/Microsoft/ChakraCore) and [build](https://github.com/Microsoft/ChakraCore/wiki/Building-ChakraCore) ChakraCore on designated platform. +From here we will assume that ChakraCore was cloned in /home/xxx/ChakraCore, and built a shared library (the default build setting). If you cloned elsewhere or are under Windows, you have to adapt accordingly the qtwrapper.pro file. +2. then get in the qtwrapper directory, and `qmake && make` and it is done: +`LD_LIBRARY_PATH='/home/xxx/ChakraCore/out/Release' ./wrappersample` + +## side note +this Qt's wrapper allows: +- to access from ChakraCore to Qt's Object +- set variables from C++ +- get variables from ChakraCore + +At least even though you're not interested by Qt, you will have examples for: +- access to variables from ChakraCore: see JSEngine::registerValue(const QString&, const QVariant&), toJsValueRef; both are in qjsengine.cpp +- register attribute's object and be able to get notified when they are changed; see: + - JSEngine::JSEngine, that register the handler that will be called by the futur object's proxy + - JSEngine::registerValue(QObject*, const QString&) that: + - register one getter and one setter (see the set and get functions in the same file) + - set the proxy by associating the handler and the object diff --git a/qtwrapper/main.cpp b/qtwrapper/main.cpp new file mode 100644 index 0000000..79e1fd3 --- /dev/null +++ b/qtwrapper/main.cpp @@ -0,0 +1,27 @@ +#include +#include "qtwrapper/qjsengine.h" +#include "qtobject.h" + +int main() +{ + QVariant result; + QStringList list; + QMap map; + QtObject *snouf = new QtObject; + + list << "a" << "b" << "c" << "d" << "1" << "2"; + map["e"] = "f";map["g"] = "h";map["i"] = 3;map["4"] = "5"; + + + chakracorejsengine::JSEngine jsEngine; + jsEngine.registerValue("nb", 46); //register a simple value + jsEngine.registerValue(snouf, "coucou"); //register a Qt's Object + jsEngine.registerValue("list", list); //register a list (as an array) + jsEngine.registerValue("map", map); //register a map (as an objet) + jsEngine.evaluate("coucou.echo('access to all variables: nb='+nb+', list[2]='+list[2]+', map.g='+map.g);"); + if (jsEngine.errorEncountered()) return EXIT_FAILURE; + result = jsEngine.evaluate("5"); //access to value returned + qDebug("Value returned: %d", result.toInt()); + + return EXIT_SUCCESS; +} diff --git a/qtwrapper/qtobject.cpp b/qtwrapper/qtobject.cpp new file mode 100644 index 0000000..adaab19 --- /dev/null +++ b/qtwrapper/qtobject.cpp @@ -0,0 +1,10 @@ +#include "qtobject.h" + +QtObject::QtObject(QObject *parent) : QObject(parent) +{ + +} +void QtObject::echo(const QString& message) +{ + qDebug("Internal value name=%s, message=%s", name.toUtf8().data(), message.toUtf8().data()); +} diff --git a/qtwrapper/qtobject.h b/qtwrapper/qtobject.h new file mode 100644 index 0000000..770c42e --- /dev/null +++ b/qtwrapper/qtobject.h @@ -0,0 +1,21 @@ +#ifndef QTOBJECT_H +#define QTOBJECT_H + +#include + +class QtObject : public QObject +{ + Q_OBJECT + QString name; +public: + Q_PROPERTY(QString name READ getName WRITE setName) + explicit QtObject(QObject *parent = nullptr); + QString getName()const{return name;} + void setName(const QString& name){this->name = name;} +signals: + +public slots: + void echo(const QString& message); +}; + +#endif // QTOBJECT_H diff --git a/qtwrapper/qtwrapper.pro b/qtwrapper/qtwrapper.pro new file mode 100644 index 0000000..ddc4b0d --- /dev/null +++ b/qtwrapper/qtwrapper.pro @@ -0,0 +1,34 @@ +QT += core +QT -= gui + +CONFIG += c++17 -rdynamic console +unix { + QMAKE_CXXFLAGS += -std=c++17 -rdynamic +} +TARGET = wrappersample +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += main.cpp \ + qtwrapper/qjsengine.cpp \ + qtobject.cpp + +DEFINES += QT_DEPRECATED_WARNINGS + + +INCLUDEPATH += /home/melidrissi/.root/Applications/ChakraCore/lib/Jsrt/ +HEADERS += \ + qtwrapper/qjsengine.h \ + qtobject.h + +unix { + LIBS += -L/home/melidrissi/.root/Applications/ChakraCore/out/Release -lChakraCore -pthread -lm -ldl -licuuc +} +win32 { + LIBS += 'C:/PATH_TO_LIBRARY/ChakraCore.Lib' + INCLUDEPATH += PATH_TO_LIBRARY_INCLUDES/include +} + +DISTFILES += \ + README.md diff --git a/qtwrapper/qtwrapper/qjsengine.cpp b/qtwrapper/qtwrapper/qjsengine.cpp new file mode 100644 index 0000000..db90609 --- /dev/null +++ b/qtwrapper/qtwrapper/qjsengine.cpp @@ -0,0 +1,612 @@ +#include "qjsengine.h" +#include "ChakraCore.h" +#include +#include +#include +#include +#include +#include +#define EXCEPTION_MAX_LENGTH 255 +#define JS_EXPORT_BASE_NAME "_XY_" +static JsValueRef caller(JsValueRef, bool, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); +static JsValueRef set(JsValueRef, bool, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); +static JsValueRef get(JsValueRef, bool, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); +static bool registerFunction(JsValueRef hostObject, const QByteArray& callbackName, JsNativeFunction callback, void *callbackState); +static bool runMethod(QObject* object, QMetaMethod& method, QVariantList& parameters, QVariant& retour); + +namespace chakracorejsengine +{ + class ErrorCode + { + bool withException; + JsErrorCode errCode; + QString errString; + public: + typedef enum {DEBUG, INFO, WARNING, CRITICAL, FATAL} ErrorLevel; + explicit ErrorCode(bool withException = true):withException(withException), errCode(JsNoError){} + explicit ErrorCode(JsErrorCode errCode, bool withException = true):withException(withException) + { + (*this) = errCode; + } + ErrorCode(const ErrorCode& errorCode) + :withException(errorCode.withException), errCode(errorCode.errorCode()) + , errString(errorCode.errString) + {} + operator bool() const + { + return errCode == JsNoError; + } + JsErrorCode errorCode() const{return errCode;} + QString errorString() const{return errString;} + ErrorCode& fatal(JsErrorCode errorCode, const QString& errorMessage = "") noexcept (false) + {return (*this)(errorCode, errorMessage, FATAL);} + ErrorCode& warning(JsErrorCode errorCode, const QString& errorMessage = "") noexcept (false) + {return (*this)(errorCode, errorMessage, WARNING);} + ErrorCode& info(JsErrorCode errorCode, const QString& errorMessage = "") noexcept (false) + {return (*this)(errorCode, errorMessage, INFO);} + ErrorCode& critical(JsErrorCode errorCode, const QString& errorMessage = "") noexcept (false) + {return (*this)(errorCode, errorMessage, CRITICAL);} + ErrorCode& debug(JsErrorCode errorCode, const QString& errorMessage = "") noexcept (false) + {return (*this)(errorCode, errorMessage, DEBUG);} + ErrorCode& operator()(JsErrorCode errorCode, const QString& errorMessage, const ErrorLevel& errorLevel = WARNING) noexcept (false) + { + this->errCode = errorCode; + if (errorCode == JsNoError) + return *this; + errString = "[%1:%2] Error on Error! Original error code=%3, new error code=%4; extra info=%5"; + char buffer [EXCEPTION_MAX_LENGTH + 1]; + size_t actualLength; + JsErrorCode error; + + JsValueRef exception; + if ((error = JsGetAndClearException(&exception)) != JsNoError) + qFatal("%s", errString.arg(__FILE__).arg(__LINE__).arg(errorCode).arg(error).arg(errorMessage).toUtf8().data()); + + JsPropertyIdRef messageName; + if ((error = JsCreatePropertyId("message", strlen("message"), &messageName)) != JsNoError) + qFatal("%s", errString.arg(__FILE__).arg(__LINE__).arg(errorCode).arg(error).arg(errorMessage).toUtf8().data()); + + JsValueRef messageValue; + if ((error = JsGetProperty(exception, messageName, &messageValue)) != JsNoError) + qFatal("%s", errString.arg(__FILE__).arg(__LINE__).arg(errorCode).arg(error).arg(errorMessage).toUtf8().data()); + if ((error = JsCopyString(messageValue, buffer, EXCEPTION_MAX_LENGTH, &actualLength)) != JsNoError) + qFatal("%s", errString.arg(__FILE__).arg(__LINE__).arg(errorCode).arg(error).arg(errorMessage).toUtf8().data()); + + buffer[actualLength] = 0; + errString = QString("[%1:%2] %3; extra info=%5").arg(__FILE__).arg(__LINE__).arg(buffer).arg(errorMessage); + switch(errorLevel) + { + case DEBUG: qDebug("%s", errString.toUtf8().data());break; + case INFO: qInfo("%s", errString.toUtf8().data());break; + case WARNING: qWarning("%s", errString.toUtf8().data());break; + case CRITICAL: qCritical("%s", errString.toUtf8().data());break; + case FATAL: qFatal("%s", errString.toUtf8().data()); + } + if (this->withException) + throw new std::runtime_error(errString.toUtf8().data()); + else + return *this; + + } + ErrorCode& operator=(JsErrorCode errorCode) noexcept (false) + { + return (*this)(errorCode, ""); + } + ErrorCode& operator=(const ErrorCode& errorCode) noexcept (true) + { + this->errCode = errorCode.errCode; + this->errString = errorCode.errString; + this->withException = errorCode.withException; + return *this; + } + }; + struct s_JSEngine_DATA + { + JsRuntimeHandle runtime; + JsContextRef context; + JsValueRef globalObject; + ErrorCode errCode; + QString errorString; + }; + static QVariant toVariant(JsValueRef value, ErrorCode* errorCode = nullptr); + static JsValueRef toJsValueRef(const QVariant& value, ErrorCode* errorCode = nullptr); + static JsErrorCode JsGetPropertyIdFromName(const QString& string, JsPropertyIdRef *propertyId) + { + return JsCreatePropertyId( + string.toUtf8().data(), + static_cast(string.toUtf8().length()), + propertyId); + } + struct s_callback_data + { + QObject* object; + QMetaMethod method; + }; + #define DATA (*reinterpret_cast(internalData)) + + JSEngine::JSEngine(QObject *parent): QObject(parent), internalData(new s_JSEngine_DATA()) + { + ErrorCode errCode; + errCode.critical(JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &DATA.runtime), QString("Error at JsCreateRuntime")); + errCode.critical(JsCreateContext(DATA.runtime, &DATA.context), "Error at JsCreateContext"); + errCode.critical(JsSetCurrentContext(DATA.context)); + errCode.critical(JsGetGlobalObject(&DATA.globalObject)); + QString handler = QString(R"END( + var %1handler = { + get: function(target, name) { + if (name in target) + return target[name]; + return target.%1get(name); + }, + set: function(target, name, value) { + if (name in target) + return; + target.%1set(name, value); + } + }; + )END").arg(JS_EXPORT_BASE_NAME); + evaluate(handler); + } + JSEngine::~JSEngine() + { + JsSetCurrentContext(JS_INVALID_REFERENCE); + JsDisposeRuntime(DATA.runtime); + delete reinterpret_cast(internalData); + } + + QVariant JSEngine::evaluate(const QString &program, const QString &fileName, int) + { + static unsigned currentSourceContext = 0; + JsValueRef result; + DATA.errCode = ErrorCode(JsNoError, false); + ErrorCode& errCode = DATA.errCode; + JsValueRef fname; + if (!(errCode = JsCreateString(fileName.toUtf8().data(), static_cast(fileName.toUtf8().size()), &fname))) + return QVariant(); + + + JsValueRef scriptSource; + if (!(errCode = JsCreateString(program.toUtf8().data(), static_cast(program.toUtf8().size()), &scriptSource))) + return QVariant(); + + if (!(errCode = JsRun(scriptSource, currentSourceContext++, fname, JsParseScriptAttributeNone, &result))) + return QVariant(); + + return toVariant(result); + } + bool JSEngine::errorEncountered() const + { + return ! DATA.errCode; + } + bool JSEngine::collectGarbage() + { + ErrorCode errCode(JsCollectGarbage(DATA.runtime), false); + return errCode; + } + QString JSEngine::errorString() const + { + return DATA.errCode.errorString(); + } + bool JSEngine::registerValue(const QString& name, const QVariant& value) + { + JsValueRef jsValue; + JsPropertyIdRef propertyId; + DATA.errCode = ErrorCode(JsNoError, false); + ErrorCode& errCode = DATA.errCode; + if (!(errCode = JsCreatePropertyId(name.toUtf8().data(), static_cast(name.toUtf8().size()), &propertyId))) + return false; + jsValue = toJsValueRef(value, &errCode); + if (!errCode) return false; + if (!(errCode = JsSetProperty(DATA.globalObject, propertyId, jsValue, true))) + return false; + return true; + } + bool JSEngine::registerValue(QObject* value, const QString& name) + { + if (value == nullptr) + qFatal("[%s:%d] Null pointer given\n", __FILE__, __LINE__); + DATA.errCode = ErrorCode(JsNoError, false); + ErrorCode& errCode = DATA.errCode; + QString objectName = name; + if (objectName.isEmpty()) + { + if (value->objectName().isEmpty()) + qFatal("[%s:%d] Asked to register an object without a name given\n", __FILE__, __LINE__); + objectName = value->objectName(); + } + JsValueRef object; + JsPropertyIdRef objectPropertyId; + + if(!(errCode = JsCreateObject(&object))) return false; + if (!(errCode = JsGetPropertyIdFromName((JS_EXPORT_BASE_NAME+objectName).toUtf8().data(), &objectPropertyId))) return false; + if (!(errCode = JsSetProperty(DATA.globalObject, objectPropertyId, object, true))) return false; + const QMetaObject* metaObject = value->metaObject(); + QMetaMethod method; + for(int i = 0, size = metaObject->methodCount(); i < size ; i++) + { + method = metaObject->method(i); + if (method.access() != QMetaMethod::Public) + continue; + s_callback_data* callbackData = new s_callback_data{value, method}; + if (registerFunction(object, method.name(), caller, callbackData) == false) + return false; + } + s_callback_data* callbackData = new s_callback_data{value, method}; + if (registerFunction(object, JS_EXPORT_BASE_NAME "get", get, callbackData) == false) return false; + if (registerFunction(object, JS_EXPORT_BASE_NAME "set", set, callbackData) == false) return false; + evaluate(QString("var %1 = new Proxy(%2%1, %2handler);4").arg(objectName).arg(JS_EXPORT_BASE_NAME)); + return errCode; + } + QVariant JSEngine::value(const QString& name, bool *ok) + { + DATA.errCode = ErrorCode(JsNoError, false); + ErrorCode& errCode = DATA.errCode; + JsPropertyIdRef propertyId; + JsValueRef symbol; + QVariant retour; + if (ok != nullptr) *ok = false; + if (!(errCode = JsGetPropertyIdFromName(name, &propertyId))) + return QVariant(); + if (!(errCode = JsGetProperty(DATA.globalObject,propertyId, &symbol))) + return QVariant(); + retour = toVariant(symbol, &errCode); + if (!errCode) + return QVariant(); + if (ok != nullptr) *ok = true; + + return retour; + } + JsValueRef toJsValueRef(const QVariant& value, ErrorCode* errorCode) + { + int i = 0, size = 0; + JsValueRef retour = JS_INVALID_REFERENCE; + JsValueRef index = JS_INVALID_REFERENCE; + JsValueRef subValue; + ErrorCode errCode(JsNoError, false); + QVariantList array; + JsPropertyIdRef propertyIdForName; + QVariantMap object; + if (value.isNull()) + errCode = JsGetNullValue(&retour); + else + switch(value.type()) + { + case QVariant::Int: + errCode = JsIntToNumber(value.toInt(), &retour); + break; + case QVariant::Double: + case QVariant::UInt: + case QVariant::ULongLong: + errCode = JsDoubleToNumber(value.toDouble(), &retour); + break; + case QVariant::Bool: + errCode = JsBoolToBoolean(value.toBool(), &retour); + break; + case QVariant::String: + errCode = JsCreateString(value.toString().toUtf8().data(), static_cast(value.toString().toUtf8().length()), &retour); + break; + case QVariant::Invalid: + errCode = JsGetUndefinedValue(&retour); + break; + case QVariant::Map: + object = value.toMap(); + if (!(errCode = JsCreateObject(&retour))) + break; + { + QMapIterator it(object); + while (it.hasNext()) + { + it.next(); + subValue = toJsValueRef(it.value(), &errCode); + if (!errCode) break; + if (!(errCode = JsGetPropertyIdFromName(it.key(), &propertyIdForName))) break; + if (!(errCode = JsSetProperty(retour, propertyIdForName, subValue, true))) break;; + } + } + break; + case QVariant::StringList: + case QVariant::List: + array = value.toList(); + if (!(errCode = JsCreateArray(static_cast(array.count()), &retour))) break; + for(i = 0, size = array.size() ; i < size ; i++) + { + if (!(errCode = JsIntToNumber(i,&index))) break; + subValue = toJsValueRef(array.at(i), &errCode); + if (!errCode) break; + errCode = JsSetIndexedProperty(retour, index, subValue); + } + break; + default: + qWarning("%s", QString("[WARNING] [%1:%2] Unsupported type; value=>%3<, typeName=%4") + .arg(__FILE__) + .arg(__LINE__) + .arg(value.toString()) + .arg(value.typeName()).toUtf8().data()); + retour = JS_INVALID_REFERENCE; + break; + } + if (errorCode != nullptr) *errorCode = errCode.errorCode(); + if (errCode.errorCode() == JsNoError) + return retour; + return JS_INVALID_REFERENCE; + } + struct s_node + { + bool isScalar; + bool isArray; + QVariantList* array; + QVariantMap* object; + QVariant* scalar; + JsValueRef* ref; + }; + QVariant convert(JsValueRef ref, ErrorCode& errorCode) + { + bool boolValue; + double doubleValue; + size_t actualLength = 0; + int i = 0; + int size = 0; + JsValueRef tmp; + QByteArray stringValue; + JsValueType valueType; + JsValueRef cell; + JsValueType cellType; + JsPropertyIdRef propertyIdForLength; + QVariantList array; + + QVariantMap object; + QString objectKey; + JsPropertyIdRef propertyIdForName; + errorCode = JsGetValueType(ref, &valueType); + if (!errorCode) return QVariant(); + + switch(valueType) + { + case JsFunction: return QString("[FUNCTION]"); + case JsError: return QString("[ERROR]"); + case JsSymbol: return QString("[SYMBOL]"); + case JsDataView: return QString("[DATAVIEW]"); + case JsArrayBuffer: return QString("[ARRAYBUFFER]"); + case JsTypedArray: return QString("[TYPEDARRAY]"); + case JsUndefined: return QVariant(QVariant::Invalid); + case JsNull: return QVariant(); + case JsBoolean: errorCode = JsBooleanToBool(ref, &boolValue);return boolValue; + case JsNumber: errorCode = JsNumberToDouble(ref, &doubleValue);return doubleValue; + case JsString: + errorCode = JsCopyString(ref, nullptr, 0, &actualLength); + stringValue.resize(static_cast(actualLength) + 1); + errorCode = JsCopyString(ref, stringValue.data(), actualLength, &actualLength); + stringValue[static_cast(actualLength)] = 0; + return QString(stringValue); + case JsObject: + JsValueRef propertyNamesArray; + if (!(errorCode = JsGetOwnPropertyNames(ref, &propertyNamesArray))) return QVariant(); + if (!(errorCode = JsGetPropertyIdFromName("length", &propertyIdForLength))) return QVariant(); + if (!(errorCode = JsGetProperty(propertyNamesArray, propertyIdForLength, &tmp))) return QVariant(); + if (!(errorCode = JsNumberToInt(tmp, &size))) return QVariant(); + for(i = 0 ; i < size ; i++) + { + if (!(errorCode = JsIntToNumber(i,&tmp))) return QVariant(); + if (!(errorCode = JsGetIndexedProperty(propertyNamesArray, tmp, &cell))) return QVariant(); + objectKey = convert(cell, errorCode).toString(); + if (!(errorCode = JsGetPropertyIdFromName(objectKey, &propertyIdForName))) return QVariant(); + if (!(errorCode = JsGetProperty(ref, propertyIdForName, &tmp))) return QVariant(); + object.insert(objectKey, convert(tmp, errorCode)); + if (!errorCode) return QVariant(); + } + + return object; + case JsArray: + if (!(errorCode = JsGetPropertyIdFromName("length", &propertyIdForLength))) return QVariant(); + if (!(errorCode = JsGetProperty(ref, propertyIdForLength, &tmp))) return QVariant(); + if (!(errorCode = JsNumberToInt(tmp, &size))) return QVariant(); + for(i = 0 ; i < size ; i++) + { + if (!(errorCode = JsIntToNumber(i,&tmp))) return QVariant(); + if (!(errorCode = JsGetIndexedProperty(ref, tmp, &cell))) return QVariant(); + if (!(errorCode = JsGetValueType(tmp, &cellType))) return QVariant(); + array.append(convert(cell, errorCode)); + if (!errorCode) return QVariant(); + } + return array; + } + return QVariant(); + } + QVariant toVariant(JsValueRef value, ErrorCode* errorCode) + { + ErrorCode errCode; + QVariant retour = convert(value, errCode); + if (errorCode != nullptr) *errorCode = errCode; + return retour; + } +} +bool registerFunction(JsValueRef hostObject, const QByteArray &callbackName, JsNativeFunction callback, void *callbackState) +{ + chakracorejsengine::ErrorCode error(false); + + JsPropertyIdRef propertyId; + if(!(error = JsCreatePropertyId(callbackName.data(), + static_cast(callbackName.length()), + &propertyId))) return false; + + JsValueRef function; + if(!(error = JsCreateFunction(callback, callbackState, &function))) return false; + if(!(error = JsSetProperty(hostObject, propertyId, function, true))) return false; + return true; +} +bool runMethod(QObject* object, QMetaMethod& method, QVariantList& parameters, QVariant& retour) +{ + Q_ASSERT(method.parameterCount() <= parameters.count()); + Q_ASSERT(object != nullptr); + bool ok; + QGenericReturnArgument returnArgument; + QList argumentForCall; + for(int i = 0 ; i < 10 ; i++) + argumentForCall.append(QGenericArgument()); + + for(int i = 0, size = method.parameterCount() ; i < size ; i++) + { + if (method.parameterType(i-1) != parameters.at(i).userType()) + { + switch(method.parameterType(i)) + { + case QMetaType::Int: + parameters[i] = QVariant(parameters.at(i).toInt()); + break; + case QMetaType::Double: + case QMetaType::UInt: + case QMetaType::ULongLong: + parameters[i] = QVariant(parameters.at(i).toDouble()); + break; + case QMetaType::Bool: + parameters[i] = QVariant(parameters.at(i).toBool()); + break; + case QMetaType::QString: + parameters[i] = QVariant(parameters.at(i).toString()); + break; + default: + qWarning("[WARNING][%s:%d] Unsupported type asked by the method=%s; pos=%d", __FILE__, __LINE__, method.methodSignature().data(), i); + return false; + } + } + argumentForCall[i] = QGenericArgument(parameters.at(i).typeName(), parameters.at(i).constData()); + } + if (method.returnType() == QMetaType::Void || method.returnType() == QMetaType::UnknownType) + { + ok = method.invoke(object, Qt::DirectConnection + , argumentForCall[0], argumentForCall[1], argumentForCall[2], argumentForCall[3] + , argumentForCall[4], argumentForCall[5], argumentForCall[6], argumentForCall[7] + , argumentForCall[8], argumentForCall[9]); + + } + else + { + retour = QVariant(QMetaType::type(method.typeName()), nullptr); + returnArgument = QGenericReturnArgument(method.typeName(), const_cast(retour.constData())); + + ok = method.invoke(object, Qt::DirectConnection, returnArgument + , argumentForCall[0], argumentForCall[1], argumentForCall[2], argumentForCall[3] + , argumentForCall[4], argumentForCall[5], argumentForCall[6], argumentForCall[7] + , argumentForCall[8], argumentForCall[9]); + } + return ok; +} +JsValueRef get(JsValueRef, bool, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + QVariant returnValue; + chakracorejsengine::ErrorCode errCode(false); + if (callbackState == nullptr) + { + qCritical("[CRITICAL][%s:%d] Caller called without callBackState\n", __FILE__, __LINE__); + return JS_INVALID_REFERENCE; + } + chakracorejsengine::s_callback_data* data = static_cast(callbackState); + if (data->object == nullptr) + { + qCritical("[CRITICAL][%s:%d] Caller called with an invalid object\n", __FILE__, __LINE__); + return JS_INVALID_REFERENCE; + } + if (argumentCount != 2) + { + qWarning("[WARNING][%s:%d] Didn't receive the right parameters number; expected=%d, received=%d\n", __FILE__, __LINE__, data->method.parameterCount(), 2); + return JS_INVALID_REFERENCE; + } + QString name = chakracorejsengine::toVariant(arguments[1], &errCode).toString(); + if (!errCode) return JS_INVALID_REFERENCE; + const QMetaObject* metaObject = data->object->metaObject(); + int idxProperty = metaObject->indexOfProperty(name.toUtf8().data()); + if (idxProperty < 0) + { + qWarning("[WARNING][%s:%d] Asked a property that doesn't exist; objectType=%s, attribut asked=%s\n", __FILE__, __LINE__, metaObject->className(), name.toUtf8().data()); + return JS_INVALID_REFERENCE; + } + + returnValue = metaObject->property(idxProperty).read(data->object); + + return chakracorejsengine::toJsValueRef(returnValue, &errCode); +} +JsValueRef set(JsValueRef, bool, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + QVariant returnValue; + chakracorejsengine::ErrorCode errCode(false); + if (callbackState == nullptr) + { + qCritical("[CRITICAL][%s:%d] Caller called without callBackState\n", __FILE__, __LINE__); + return JS_INVALID_REFERENCE; + } + chakracorejsengine::s_callback_data* data = static_cast(callbackState); + if (data->object == nullptr) + { + qCritical("[CRITICAL][%s:%d] Caller called with an invalid object\n", __FILE__, __LINE__); + return JS_INVALID_REFERENCE; + } + if (argumentCount != 3) + { + qWarning("[WARNING][%s:%d] Didn't receive the right parameters number; expected=%d, received=%d\n", __FILE__, __LINE__, data->method.parameterCount(), 3); + return JS_INVALID_REFERENCE; + } + QString name = chakracorejsengine::toVariant(arguments[1], &errCode).toString(); + if (!errCode) return JS_INVALID_REFERENCE; + QVariant value = chakracorejsengine::toVariant(arguments[2], &errCode); + if (!errCode) return JS_INVALID_REFERENCE; + const QMetaObject* metaObject = data->object->metaObject(); + int idxProperty = metaObject->indexOfProperty(name.toUtf8().data()); + if (idxProperty < 0) + { + qWarning("[WARNING][%s:%d] Asked a property that doesn't exist; objectType=%s, attribut asked=%s\n", __FILE__, __LINE__, metaObject->className(), name.toUtf8().data()); + return JS_INVALID_REFERENCE; + } + + if (! metaObject->property(idxProperty).write(data->object, value)) + qWarning("[WARNING][%s:%d] Write the propery didn't work; objectType=%s, property=%s, value=%s\n", __FILE__, __LINE__, metaObject->className(), name.toUtf8().data(), value.toString().toUtf8().data()); + + + return JS_INVALID_REFERENCE; +} +JsValueRef caller(JsValueRef, bool, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + bool ok; + QVariant returnValue; + QGenericReturnArgument returnArgument; + chakracorejsengine::ErrorCode errCode(false); + if (callbackState == nullptr) + { + qCritical("[CRITICAL][%s:%d] Caller called without callBackState\n", __FILE__, __LINE__); + return JS_INVALID_REFERENCE; + } + chakracorejsengine::s_callback_data* data = static_cast(callbackState); + if (data->object == nullptr) + { + qCritical("[CRITICAL][%s:%d] Caller called with an invalid object\n", __FILE__, __LINE__); + return JS_INVALID_REFERENCE; + } + if (data->method.parameterCount() != argumentCount-1) + { + qWarning("[WARNING][%s:%d] Didn't receive the right parameters number; expected=%d, received=%d\n", __FILE__, __LINE__, data->method.parameterCount(), argumentCount-1); + return JS_INVALID_REFERENCE; + } + if (argumentCount > 11) + { + qWarning("[WARNING][%s:%d] Received too much parameters; max expected=10, received=%d\n", __FILE__, __LINE__, argumentCount-1); + return JS_INVALID_REFERENCE; + } + QList argumentForCall; + for(int i = 0 ; i < 10 ; i++) + argumentForCall.append(QGenericArgument()); + QVariantList variantArguments; + for(int i = 1, size = argumentCount ; i < size ; i++) + { + variantArguments.append(chakracorejsengine::toVariant(arguments[i], &errCode)); + if (!errCode) + return JS_INVALID_REFERENCE; + } + ok = runMethod(data->object, data->method, variantArguments, returnValue); + if (!ok) + { + qWarning("[WARNING][%s:%d] Call of >%s< failed", __FILE__, __LINE__, data->method.methodSignature().data()); + return JS_INVALID_REFERENCE; + } + if (!returnValue.isValid()) + return JS_INVALID_REFERENCE; + return chakracorejsengine::toJsValueRef(returnValue, &errCode); +} diff --git a/qtwrapper/qtwrapper/qjsengine.h b/qtwrapper/qtwrapper/qjsengine.h new file mode 100644 index 0000000..3a31e37 --- /dev/null +++ b/qtwrapper/qtwrapper/qjsengine.h @@ -0,0 +1,35 @@ +#ifndef QJSENGINE_H +#define QJSENGINE_H + +#include + + +namespace chakracorejsengine +{ +/** + * @brief The JSEngine class + * + * You must create ONE JSEngine per thread at most! + */ +class JSEngine : public QObject +{ + Q_OBJECT +public: + explicit JSEngine(QObject *parent = nullptr); + virtual ~JSEngine(); + + QVariant evaluate(const QString &program, const QString &fileName = QString(), int lineNumber = 1); + bool registerValue(const QString& name, const QVariant& value); + bool registerValue(QObject* value, const QString& name = QString()); + QVariant value(const QString& name, bool *ok = nullptr); + QString errorString() const; + bool errorEncountered() const; + bool collectGarbage(); + +private: + Q_DISABLE_COPY(JSEngine) + void* internalData; +}; +} + +#endif // QJSENGINE_H