if (conversionFailures) { if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { parameterConversionFailureCount = conversionFailures; parameterConversionFailureId = meta.methodIndex(); } continue; } ScriptContextQtWrapper ourContext(_engine, context); ScriptContextGuard guard(&ourContext); int returnTypeId = meta.returnType(); // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's // nothing in there for return values so these need to be explicitly runtime-registered! Q_ASSERT(returnTypeId != QMetaType::UnknownType); if (returnTypeId == QMetaType::UnknownType) { context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName())); return QVariant(); } else if (returnTypeId == QMetaType::Void) { bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); } return QVariant(); } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); return QVariant(); } QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result); return QVariant::fromValue(qResult); } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); bool success = meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); return QVariant(); } QScriptValue qResult = _engine->castVariantToValue(qRetVal); return QVariant::fromValue(qResult); } } // we failed to convert the call to C++, try to create a somewhat sane error message if (parameterConversionFailureCount == 0) { context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName())); return QVariant(); } const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); int methodNumArgs = meta.parameterCount(); Q_ASSERT(methodNumArgs == numArgs); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QScriptValue argVal = context->argument(arg); if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); return QVariant(); } } } Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? return QVariant(); context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()));} QString ScriptSignalQtProxy::fullName() const { Q_ASSERT(_object); if (!_object) return ""; QString objectName = _object->objectName(); if (!objectName.isEmpty()) { return QString("%1.%2").arg(objectName, _meta.name()); } return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name());} // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for itint ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { id = ScriptSignalQtProxyBase::qt_metacall(call, id, arguments); if (id != 0 || call != QMetaObject::InvokeMetaMethod) { return id; } QScriptValueList args; int numArgs = _meta.parameterCount(); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QVariant argValue(methodArgTypeId, arguments[arg+1]); args.append(_engine->castVariantToValue(argValue)); } QList<Connection> connections; withReadLock([&]{ connections = _connections; }); for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { Connection& conn = *iter; conn.callback.call(conn.thisValue, args); } return -1;} int ScriptSignalQtProxy::discoverMetaCallIdx() { const QMetaObject* ourMeta = metaObject(); return ourMeta->methodCount();} ScriptSignalQtProxy::ConnectionList::iterator ScriptSignalQtProxy::findConnection(QScriptValue thisObject, QScriptValue callback) { ConnectionList::iterator iter; withReadLock([&]{ for (iter = _connections.begin(); iter != _connections.end(); ++iter) { Connection& conn = *iter; if (conn.callback.strictlyEquals(callback) && conn.thisValue.strictlyEquals(thisObject)) { break; } } }); return iter;} void ScriptSignalQtProxy::connect(QScriptValue arg0, QScriptValue arg1) { QObject* qobject = _object; if (!qobject) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); return; } // untangle the arguments QScriptValue callback; QScriptValue callbackThis; if (arg1.isFunction()) { callbackThis = arg0; callback = arg1; } else { callback = arg0; } if (!callback.isFunction()) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'connect'"); return; } // are we already connected? ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup != _connections.end()) { return; // already exists } // add a reference to ourselves to the destination callback QScriptValue destData = callback.data(); Q_ASSERT(!destData.isValid() || destData.isArray()); if (!destData.isArray()) { destData = static_cast<QScriptEngine*>(_engine)->newArray(); } { QScriptValueList args; args << thisObject(); destData.property("push").call(destData, args); } callback.setData(destData); // add this to our internal list of connections Connection newConn; newConn.callback = callback; newConn.thisValue = callbackThis; withWriteLock([&]{ _connections.append(newConn); }); // inform Qt that we're connecting to this signal if (!_isConnected) { auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = true; }} void ScriptSignalQtProxy::disconnect(QScriptValue arg0, QScriptValue arg1) { QObject* qobject = _object; if (!qobject) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); return; } // untangle the arguments QScriptValue callback; QScriptValue callbackThis; if (arg1.isFunction()) { callbackThis = arg0; callback = arg1; } else { callback = arg0; } if (!callback.isFunction()) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'disconnect'"); return; } // locate this connection in our list of connections ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup == _connections.end()) { return; // not here } // remove it from our internal list of connections withWriteLock([&]{ _connections.erase(lookup); }); // remove a reference to ourselves from the destination callback QScriptValue destData = callback.data(); Q_ASSERT(destData.isArray()); if (destData.isArray()) { QScriptValue qThis = thisObject(); int len = destData.property("length").toInteger(); bool foundIt = false; for (int idx = 0; idx < len && !foundIt; ++idx) { QScriptValue entry = destData.property(idx); if (entry.strictlyEquals(qThis)) { foundIt = true; QScriptValueList args; args << idx << 1; destData.property("splice").call(destData, args); } } Q_ASSERT(foundIt); } // inform Qt that we're no longer connected to this signal if (_connections.empty()) { Q_ASSERT(_isConnected); bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = false; }}Lines 501–764 of 764. Previous lines
if (conversionFailures) { if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { parameterConversionFailureCount = conversionFailures; parameterConversionFailureId = meta.methodIndex(); } continue; } ScriptContextQtWrapper ourContext(_engine, context); ScriptContextGuard guard(&ourContext); int returnTypeId = meta.returnType(); // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's // nothing in there for return values so these need to be explicitly runtime-registered! Q_ASSERT(returnTypeId != QMetaType::UnknownType); if (returnTypeId == QMetaType::UnknownType) { context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName())); return QVariant(); } else if (returnTypeId == QMetaType::Void) { bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); } return QVariant(); } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); return QVariant(); } QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result); return QVariant::fromValue(qResult); } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); bool success = meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); return QVariant(); } QScriptValue qResult = _engine->castVariantToValue(qRetVal); return QVariant::fromValue(qResult); } } // we failed to convert the call to C++, try to create a somewhat sane error message if (parameterConversionFailureCount == 0) { context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName())); return QVariant(); } const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); int methodNumArgs = meta.parameterCount(); Q_ASSERT(methodNumArgs == numArgs); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QScriptValue argVal = context->argument(arg); if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); return QVariant(); } } } Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? return QVariant(); context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); } QString ScriptSignalQtProxy::fullName() const { Q_ASSERT(_object); if (!_object) return ""; QString objectName = _object->objectName(); if (!objectName.isEmpty()) { return QString("%1.%2").arg(objectName, _meta.name()); } return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); } // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { id = ScriptSignalQtProxyBase::qt_metacall(call, id, arguments); if (id != 0 || call != QMetaObject::InvokeMetaMethod) { return id; } QScriptValueList args; int numArgs = _meta.parameterCount(); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QVariant argValue(methodArgTypeId, arguments[arg+1]); args.append(_engine->castVariantToValue(argValue)); } QList<Connection> connections; withReadLock([&]{ connections = _connections; }); for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { Connection& conn = *iter; conn.callback.call(conn.thisValue, args); } return -1; } int ScriptSignalQtProxy::discoverMetaCallIdx() { const QMetaObject* ourMeta = metaObject(); return ourMeta->methodCount(); } ScriptSignalQtProxy::ConnectionList::iterator ScriptSignalQtProxy::findConnection(QScriptValue thisObject, QScriptValue callback) { ConnectionList::iterator iter; withReadLock([&]{ for (iter = _connections.begin(); iter != _connections.end(); ++iter) { Connection& conn = *iter; if (conn.callback.strictlyEquals(callback) && conn.thisValue.strictlyEquals(thisObject)) { break; } } }); return iter; } void ScriptSignalQtProxy::connect(QScriptValue arg0, QScriptValue arg1) { QObject* qobject = _object; if (!qobject) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); return; } // untangle the arguments QScriptValue callback; QScriptValue callbackThis; if (arg1.isFunction()) { callbackThis = arg0; callback = arg1; } else { callback = arg0; } if (!callback.isFunction()) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'connect'"); return; } // are we already connected? ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup != _connections.end()) { return; // already exists } // add a reference to ourselves to the destination callback QScriptValue destData = callback.data(); Q_ASSERT(!destData.isValid() || destData.isArray()); if (!destData.isArray()) { destData = static_cast<QScriptEngine*>(_engine)->newArray(); } { QScriptValueList args; args << thisObject(); destData.property("push").call(destData, args); } callback.setData(destData); // add this to our internal list of connections Connection newConn; newConn.callback = callback; newConn.thisValue = callbackThis; withWriteLock([&]{ _connections.append(newConn); }); // inform Qt that we're connecting to this signal if (!_isConnected) { auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = true; } } void ScriptSignalQtProxy::disconnect(QScriptValue arg0, QScriptValue arg1) { QObject* qobject = _object; if (!qobject) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); return; } // untangle the arguments QScriptValue callback; QScriptValue callbackThis; if (arg1.isFunction()) { callbackThis = arg0; callback = arg1; } else { callback = arg0; } if (!callback.isFunction()) { QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'disconnect'"); return; } // locate this connection in our list of connections ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup == _connections.end()) { return; // not here } // remove it from our internal list of connections withWriteLock([&]{ _connections.erase(lookup); }); // remove a reference to ourselves from the destination callback QScriptValue destData = callback.data(); Q_ASSERT(destData.isArray()); if (destData.isArray()) { QScriptValue qThis = thisObject(); int len = destData.property("length").toInteger(); bool foundIt = false; for (int idx = 0; idx < len && !foundIt; ++idx) { QScriptValue entry = destData.property(idx); if (entry.strictlyEquals(qThis)) { foundIt = true; QScriptValueList args; args << idx << 1; destData.property("splice").call(destData, args); } } Q_ASSERT(foundIt); } // inform Qt that we're no longer connected to this signal if (_connections.empty()) { Q_ASSERT(_isConnected); bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = false; } }