History

Continued tightening and testing of the scripting engine "glue" logic - QObject wrappers will now be reused when asked to wrap the same QObject multiple times - improved error reporting when we can't form a native call from script - dropped support for having the existing QtScript logic handle Qt object transformations - assuming that our code will handle all calls into native objects - changing timer functions to use CTimer* publicly rather than casted QObject*

Heather Anderson committed 4 years ago

Commit 55b5a2c1 parent 742fa2211 files · +262 −382Browse files

Showing the changes to libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp (1 of 11 files). Show all

Unified · Split

libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp+134−28
@@ -53,6 +53,18 @@ private: // storage · 52 unmodified lines
53QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object,53QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object,
54 ScriptEngine::ValueOwnership ownership,54 ScriptEngine::ValueOwnership ownership,
55 const ScriptEngine::QObjectWrapOptions& options) {55 const ScriptEngine::QObjectWrapOptions& options) {
56 QScriptEngine* qengine = static_cast<QScriptEngine*>(engine);
57
58 // do we already have a valid wrapper for this QObject?
59 {
60 QMutexLocker guard(&engine->_qobjectWrapperMapProtect);
61 ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);
62 if (lookup != engine->_qobjectWrapperMap.end()) {
63 QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock();
64 if (proxy) return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;
65 }
66 }
67
56 bool ownsObject;68 bool ownsObject;
57 switch (ownership) {69 switch (ownership) {
58 case ScriptEngine::QtOwnership:70 case ScriptEngine::QtOwnership:
@@ -65,8 +77,33 @@ QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObje · 6 unmodified lines
65 ownsObject = !object->parent();77 ownsObject = !object->parent();
66 break;78 break;
67 }79 }
68 QScriptEngine* qengine = static_cast<QScriptEngine*>(engine);80
81 // create the wrapper
69 auto proxy = QSharedPointer<ScriptObjectQtProxy>::create(engine, object, ownsObject, options);82 auto proxy = QSharedPointer<ScriptObjectQtProxy>::create(engine, object, ownsObject, options);
83
84 {
85 QMutexLocker guard(&engine->_qobjectWrapperMapProtect);
86
87 // check again to see if someone else created the wrapper while we were busy
88 ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);
89 if (lookup != engine->_qobjectWrapperMap.end()) {
90 QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock();
91 if (proxy) return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;
92 }
93
94 // register the wrapper with the engine and make sure it cleans itself up
95 engine->_qobjectWrapperMap.insert(object, proxy);
96 QPointer<ScriptEngineQtScript> enginePtr = engine;
97 object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() {
98 if (!enginePtr) return;
99 QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect);
100 ScriptEngineQtScript::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object);
101 if (lookup != enginePtr->_qobjectWrapperMap.end()) {
102 enginePtr->_qobjectWrapperMap.erase(lookup);
103 }
104 });
105 }
106
70 return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));107 return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));
71}108}
72 109
@@ -96,7 +133,6 @@ void ScriptObjectQtProxy::investigate() { · 23 unmodified lines
96 if (!qobject) return;133 if (!qobject) return;
97 134
98 const QMetaObject* metaObject = qobject->metaObject();135 const QMetaObject* metaObject = qobject->metaObject();
99 _name = QString::fromLatin1(metaObject->className());
100 136
101 // discover properties137 // discover properties
102 int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0;138 int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0;
@@ -185,6 +221,15 @@ void ScriptObjectQtProxy::investigate() { · 82 unmodified lines
185 }221 }
186}222}
187 223
224QString ScriptObjectQtProxy::name() const {
225 Q_ASSERT(_object);
226 if (!_object) return "";
227 return _object ? _object->objectName() : "";
228 QString objectName = _object->objectName();
229 if (!objectName.isEmpty()) return objectName;
230 return _object->metaObject()->className();
231}
232
188QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) {233QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) {
189 // check for properties234 // check for properties
190 for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {235 for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {
@@ -268,7 +313,7 @@ QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QSc · 77 unmodified lines
268 MethodDefMap::const_iterator lookup = _methods.find(methodId);313 MethodDefMap::const_iterator lookup = _methods.find(methodId);
269 if (lookup == _methods.cend()) return QScriptValue();314 if (lookup == _methods.cend()) return QScriptValue();
270 return static_cast<QScriptEngine*>(_engine)->newObject(315 return static_cast<QScriptEngine*>(_engine)->newObject(
271 new ScriptMethodQtProxy(_engine, qobject, object, name, lookup.value().methods));316 new ScriptMethodQtProxy(_engine, qobject, object, lookup.value().methods));
272 }317 }
273 case SIGNAL_TYPE: {318 case SIGNAL_TYPE: {
274 int signalId = id & ~TYPE_MASK;319 int signalId = id & ~TYPE_MASK;
@@ -278,7 +323,7 @@ QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QSc · 3 unmodified lines
278 InstanceMap::const_iterator instLookup = _signalInstances.find(signalId);323 InstanceMap::const_iterator instLookup = _signalInstances.find(signalId);
279 if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) {324 if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) {
280 instLookup = _signalInstances.insert(signalId,325 instLookup = _signalInstances.insert(signalId,
281 new ScriptSignalQtProxy(_engine, qobject, object, name, defLookup.value().signal));326 new ScriptSignalQtProxy(_engine, qobject, object, defLookup.value().signal));
282 Q_ASSERT(instLookup != _signalInstances.cend());327 Q_ASSERT(instLookup != _signalInstances.cend());
283 }328 }
284 ScriptSignalQtProxy* proxy = instLookup.value();329 ScriptSignalQtProxy* proxy = instLookup.value();
@@ -315,10 +360,11 @@ void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& · 30 unmodified lines
315 ScriptContextGuard guard(&ourContext);360 ScriptContextGuard guard(&ourContext);
316 361
317 int propTypeId = prop.userType();362 int propTypeId = prop.userType();
363 Q_ASSERT(propTypeId != QMetaType::UnknownType);
318 QVariant varValue;364 QVariant varValue;
319 if(!_engine->castValueToVariant(value, varValue, propTypeId)) {365 if(!_engine->castValueToVariant(value, varValue, propTypeId)) {
320 QByteArray propTypeName = QMetaType(propTypeId).name();366 QByteArray propTypeName = QMetaType(propTypeId).name();
321 QByteArray valTypeName = value.toVariant().typeName();367 QByteArray valTypeName = _engine->valueType(value).toLatin1();
322 QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext();368 QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext();
323 currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName));369 currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName));
324 return;370 return;
@@ -352,6 +398,18 @@ QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) { · 27 unmodified lines
352 return proxy ? proxy->toQtValue() : QVariant();398 return proxy ? proxy->toQtValue() : QVariant();
353}399}
354 400
401QString ScriptMethodQtProxy::fullName() const {
402 Q_ASSERT(_object);
403 if (!_object) return "";
404 Q_ASSERT(!_metas.isEmpty());
405 const QMetaMethod& firstMethod = _metas.front();
406 QString objectName = _object->objectName();
407 if (!objectName.isEmpty()) {
408 return QString("%1.%2").arg(objectName, firstMethod.name());
409 }
410 return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name());
411}
412
355bool ScriptMethodQtProxy::supportsExtension(Extension extension) const {413bool ScriptMethodQtProxy::supportsExtension(Extension extension) const {
356 switch (extension) {414 switch (extension) {
357 case Callable:415 case Callable:
@@ -377,6 +435,9 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 19 unmodified lines
377 435
378 const int scriptValueTypeId = qMetaTypeId<ScriptValue>();436 const int scriptValueTypeId = qMetaTypeId<ScriptValue>();
379 437
438 int parameterConversionFailureId = 0;
439 int parameterConversionFailureCount = 0;
440
380 for (auto iter = _metas.cbegin(); iter != _metas.end(); ++iter) {441 for (auto iter = _metas.cbegin(); iter != _metas.end(); ++iter) {
381 const QMetaMethod& meta = *iter;442 const QMetaMethod& meta = *iter;
382 int methodNumArgs = meta.parameterCount();443 int methodNumArgs = meta.parameterCount();
@@ -387,10 +448,10 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 4 unmodified lines
387 QList<ScriptValue> qScriptArgList;448 QList<ScriptValue> qScriptArgList;
388 QList<QVariant> qVarArgList;449 QList<QVariant> qVarArgList;
389 QGenericArgument qGenArgs[10];450 QGenericArgument qGenArgs[10];
390 int arg;451 int conversionFailures = 0;
391 bool conversionFailed = false;452 for (int arg = 0; arg < numArgs; ++arg) {
392 for (arg = 0; arg < numArgs && !conversionFailed; ++arg) {
393 int methodArgTypeId = meta.parameterType(arg);453 int methodArgTypeId = meta.parameterType(arg);
454 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
394 QScriptValue argVal = context->argument(arg);455 QScriptValue argVal = context->argument(arg);
395 if (methodArgTypeId == scriptValueTypeId) {456 if (methodArgTypeId == scriptValueTypeId) {
396 qScriptArgList.append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal)));457 qScriptArgList.append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal)));
@@ -401,24 +462,23 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 4 unmodified lines
401 } else {462 } else {
402 QVariant varArgVal;463 QVariant varArgVal;
403 if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {464 if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {
404 conversionFailed = true;465 conversionFailures++;
405 break;466 } else {
406 /*QByteArray methodTypeName = QMetaType(methodArgTypeId).name();467 qVarArgList.append(varArgVal);
407 QByteArray argTypeName = argVal.toVariant().typeName();468 const QVariant& converted = qVarArgList.back();
408 context->throwError(QScriptContext::TypeError,469
409 QString("Cannot convert %1 to %2").arg(argTypeName, methodTypeName));470 // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
410 return QVariant();*/471 // A const_cast is needed because calling data() would detach the QVariant.
472 qGenArgs[arg] =
473 QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData()));
411 }474 }
412 qVarArgList.append(varArgVal);
413 const QVariant& converted = qVarArgList.back();
414
415 // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
416 // A const_cast is needed because calling data() would detach the QVariant.
417 qGenArgs[arg] =
418 QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData()));
419 }475 }
420 }476 }
421 if (conversionFailed) {477 if (conversionFailures) {
478 if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) {
479 parameterConversionFailureCount = conversionFailures;
480 parameterConversionFailureId = meta.methodIndex();
481 }
422 continue;482 continue;
423 }483 }
424 484
@@ -426,11 +486,18 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 1 unmodified line
426 ScriptContextGuard guard(&ourContext);486 ScriptContextGuard guard(&ourContext);
427 487
428 int returnTypeId = meta.returnType();488 int returnTypeId = meta.returnType();
429 if (returnTypeId == QMetaType::Void) {489
490 // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's
491 // nothing in there for return values so these need to be explicitly runtime-registered!
492 Q_ASSERT(returnTypeId != QMetaType::UnknownType);
493 if (returnTypeId == QMetaType::UnknownType) {
494 context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()));
495 return QVariant();
496 } else if (returnTypeId == QMetaType::Void) {
430 bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],497 bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
431 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);498 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
432 if (!success) {499 if (!success) {
433 context->throwError("Native call failed");500 context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName()));
434 }501 }
435 return QVariant();502 return QVariant();
436 } else if (returnTypeId == scriptValueTypeId) {503 } else if (returnTypeId == scriptValueTypeId) {
@@ -439,7 +506,7 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 2 unmodified lines
439 qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6],506 qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6],
440 qGenArgs[7], qGenArgs[8], qGenArgs[9]);507 qGenArgs[7], qGenArgs[8], qGenArgs[9]);
441 if (!success) {508 if (!success) {
442 context->throwError("Native call failed");509 context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName()));
443 return QVariant();510 return QVariant();
444 }511 }
445 QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result);512 QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result);
@@ -454,15 +521,53 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 8 unmodified lines
454 meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],521 meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
455 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);522 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
456 if (!success) {523 if (!success) {
457 context->throwError("Native call failed");524 context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName()));
458 return QVariant();525 return QVariant();
459 }526 }
460 QScriptValue qResult = _engine->castVariantToValue(qRetVal);527 QScriptValue qResult = _engine->castVariantToValue(qRetVal);
461 return QVariant::fromValue(qResult);528 return QVariant::fromValue(qResult);
462 }529 }
463 }530 }
464 context->throwError("Native call failed: could not locate an overload with the requested arguments");531
532 // we failed to convert the call to C++, try to create a somewhat sane error message
533 if (parameterConversionFailureCount == 0) {
534 context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName()));
535 return QVariant();
536 }
537
538 const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId);
539 int methodNumArgs = meta.parameterCount();
540 Q_ASSERT(methodNumArgs == numArgs);
541
542 for (int arg = 0; arg < numArgs; ++arg) {
543 int methodArgTypeId = meta.parameterType(arg);
544 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
545 QScriptValue argVal = context->argument(arg);
546 if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) {
547 QVariant varArgVal;
548 if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {
549 QByteArray methodTypeName = QMetaType(methodArgTypeId).name();
550 QByteArray argTypeName = _engine->valueType(argVal).toLatin1();
551 context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4")
552 .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName));
553 return QVariant();
554 }
555 }
556 }
557
558 Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now?
465 return QVariant();559 return QVariant();
560 context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()));
561}
562
563QString ScriptSignalQtProxy::fullName() const {
564 Q_ASSERT(_object);
565 if (!_object) return "";
566 QString objectName = _object->objectName();
567 if (!objectName.isEmpty()) {
568 return QString("%1.%2").arg(objectName, _meta.name());
569 }
570 return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name());
466}571}
467 572
468// Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it573// Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it
@@ -476,6 +581,7 @@ int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** argu · 7 unmodified lines
476 int numArgs = _meta.parameterCount();581 int numArgs = _meta.parameterCount();
477 for (int arg = 0; arg < numArgs; ++arg) {582 for (int arg = 0; arg < numArgs; ++arg) {
478 int methodArgTypeId = _meta.parameterType(arg);583 int methodArgTypeId = _meta.parameterType(arg);
584 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
479 QVariant argValue(methodArgTypeId, arguments[arg+1]);585 QVariant argValue(methodArgTypeId, arguments[arg+1]);
480 args.append(_engine->castVariantToValue(argValue));586 args.append(_engine->castVariantToValue(argValue));
481 }587 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t