diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-06-02 21:01:24 +0200 |
---|---|---|
committer | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-06-02 21:01:24 +0200 |
commit | 7b09ec10b257bc2ee8fd08174a111f0cb5d7f93b (patch) | |
tree | dbcfd75e6427f71f49095b8e5066de0712b9b027 | |
parent | ba171e01f90e9399ca4bfab0204b2932380ad64f (diff) | |
parent | c107dbf2aa7c5c603a23f543fe68c9c77ec822df (diff) |
Merge "Merge remote-tracking branch 'origin/5.12' into 5.13"
23 files changed, 682 insertions, 213 deletions
diff --git a/src/remoteobjects/qremoteobjectdynamicreplica.cpp b/src/remoteobjects/qremoteobjectdynamicreplica.cpp index 58b8778..a004593 100644 --- a/src/remoteobjects/qremoteobjectdynamicreplica.cpp +++ b/src/remoteobjects/qremoteobjectdynamicreplica.cpp @@ -171,6 +171,7 @@ int QRemoteObjectDynamicReplica::qt_metacall(QMetaObject::Call call, int id, voi id = -1; } else if (call == QMetaObject::InvokeMetaMethod) { if (id < impl->m_numSignals) { + qCDebug(QT_REMOTEOBJECT) << "DynamicReplica Activate" << impl->m_metaObject->method(saved_id).methodSignature(); // signal relay from Source world to Replica QMetaObject::activate(this, impl->m_metaObject, id, argv); @@ -183,10 +184,21 @@ int QRemoteObjectDynamicReplica::qt_metacall(QMetaObject::Call call, int id, voi QVariantList args; args.reserve(typeSize); for (int i = 0; i < typeSize; ++i) { - if (impl->m_metaObject->indexOfEnumerator(types[i].constData()) != -1) - args.push_back(QVariant(QMetaType::Int, argv[i + 1])); - else - args.push_back(QVariant(QMetaType::type(types[i].constData()), argv[i + 1])); + const int type = QMetaType::type(types[i].constData()); + if (impl->m_metaObject->indexOfEnumerator(types[i].constData()) != -1) { + const auto size = QMetaType(type).sizeOf(); + switch (size) { + case 1: args.push_back(QVariant(QMetaType::Char, argv[i + 1])); break; + case 2: args.push_back(QVariant(QMetaType::Short, argv[i + 1])); break; + case 4: args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break; + // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int) +// case 8: args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break; + default: + qWarning() << "Invalid enum detected (Dynamic Replica)" << QMetaType::typeName(type) << "with size" << size; + args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break; + } + } else + args.push_back(QVariant(type, argv[i + 1])); } if (debugArgs) { diff --git a/src/remoteobjects/qremoteobjectnode.cpp b/src/remoteobjects/qremoteobjectnode.cpp index d241e15..127ed9d 100644 --- a/src/remoteobjects/qremoteobjectnode.cpp +++ b/src/remoteobjects/qremoteobjectnode.cpp @@ -118,6 +118,42 @@ static void GadgetLoadOperator(QDataStream &in, void *data) in >> prop; } +// Like the Q_GADGET static methods above, we need constructor/destructor methods +// in order to use dynamically defined enums with QVariant or as signal/slot +// parameters (i.e., the queued connection mechanism, which QtRO leverages). +// +// We will need the enum methods to support different sizes when typed scope enum +// support is added, so might as well use that now. +template<typename T> +static void EnumDestructor(void *ptr) +{ + static_cast<T*>(ptr)->~T(); +} + +template<typename T> +static void *EnumConstructor(void *where, const void *copy) +{ + T *ret = where ? new(where) T : new T; + if (copy) + *ret = *static_cast<const T*>(copy); + return ret; +} + +// Not used, but keeping these in case we end up with a need for save/load. +template<typename T> +static void EnumSaveOperator(QDataStream & out, const void *data) +{ + const T value = *static_cast<const T *>(data); + out << value; +} + +template<typename T> +static void EnumLoadOperator(QDataStream &in, void *data) +{ + T value = *static_cast<T *>(data); + in >> value; +} + static QString name(const QMetaObject * const mobj) { const int ind = mobj->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE); @@ -659,9 +695,67 @@ static void trackConnection(int typeId, IoDeviceBase *connection) QObject::connect(connection, &IoDeviceBase::destroyed, unregisterIfNotUsed); } -static int registerGadget(IoDeviceBase *connection, QRemoteObjectPackets::GadgetsData &gadgets, QByteArray typeName) +struct EnumPair { + QByteArray name; + int value; +}; + +struct EnumData { + QByteArray name; + bool isFlag, isScoped; + quint32 keyCount, size; + QVector<EnumPair> values; +}; + +struct GadgetProperty { + QByteArray name; + QByteArray type; +}; + +struct GadgetData { + QVector<GadgetProperty> properties; + QVector<EnumData> enums; +}; + +using Gadgets = QHash<QByteArray, GadgetData>; + +static void registerEnum(const QByteArray &name, const QMetaObject *meta, int size=4) +{ + // When we add support for enum classes, we will need to set this to something like + // QByteArray(enumClass).append("::").append(enumMeta.name()) when enumMeta.isScoped() is true. + // That is a new feature, though. + if (QMetaType::isRegistered(QMetaType::type(name))) + return; + static const auto flags = QMetaType::IsEnumeration | QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + int id; + switch (size) { + case 1: id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor<qint8>, + &EnumConstructor<qint8>, size, flags, meta); + break; + case 2: id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor<qint16>, + &EnumConstructor<qint16>, size, flags, meta); + break; + case 4: id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor<qint32>, + &EnumConstructor<qint32>, size, flags, meta); + break; + // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int) +// case 8: id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor<qint64>, +// &EnumConstructor<qint64>, size, flags, meta); +// break; + default: + qWarning() << "Invalid enum detected" << name << "with size" << size << ". Defaulting to register as int."; + id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor<qint32>, + &EnumConstructor<qint32>, size, flags, meta); + } +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug() << "Registering new enum with id" << id << name << "size:" << size; +#endif + qCDebug(QT_REMOTEOBJECT) << "Registering new enum with id" << id << name << "size:" << size; +} + +static int registerGadgets(IoDeviceBase *connection, Gadgets &gadgets, QByteArray typeName) { - const auto &properties = gadgets.take(typeName); + const auto &gadget = gadgets.take(typeName); int typeId = QMetaType::type(typeName); if (typeId != QMetaType::UnknownType) { trackConnection(typeId, connection); @@ -672,52 +766,110 @@ static int registerGadget(IoDeviceBase *connection, QRemoteObjectPackets::Gadget gadgetBuilder.setClassName(typeName); gadgetBuilder.setFlags(QMetaObjectBuilder::DynamicMetaObject | QMetaObjectBuilder::PropertyAccessInStaticMetaCall); GadgetType gadgetType; - for (const auto &prop : properties) { + for (const auto &prop : gadget.properties) { int propertyType = QMetaType::type(prop.type); if (!propertyType && gadgets.contains(prop.type)) - propertyType = registerGadget(connection, gadgets, prop.type); + propertyType = registerGadgets(connection, gadgets, prop.type); gadgetType.push_back(QVariant(QVariant::Type(propertyType))); auto dynamicProperty = gadgetBuilder.addProperty(prop.name, prop.type); dynamicProperty.setWritable(true); dynamicProperty.setReadable(true); } + for (const auto &enumData: gadget.enums) { + auto enumBuilder = gadgetBuilder.addEnumerator(enumData.name); + enumBuilder.setIsFlag(enumData.isFlag); + enumBuilder.setIsScoped(enumData.isScoped); + + for (quint32 k = 0; k < enumData.keyCount; ++k) { + const auto pair = enumData.values.at(k); + enumBuilder.addKey(pair.name, pair.value); + } + } auto meta = gadgetBuilder.toMetaObject(); - meta->d.static_metacall = &GadgetsStaticMetacallFunction; - meta->d.superdata = nullptr; - const auto flags = QMetaType::IsGadget | QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; - int gadgetTypeId = QMetaType::registerType(typeName.constData(), + const auto enumCount = meta->enumeratorCount(); + for (int i = 0; i < enumCount; i++) { + const QByteArray registeredName = QByteArray(typeName).append("::").append(meta->enumerator(i).name()); + registerEnum(registeredName, meta, gadget.enums.at(i).size); + } + QMetaType::TypeFlags flags = QMetaType::IsGadget; + int gadgetTypeId; + if (meta->propertyCount()) { + meta->d.static_metacall = &GadgetsStaticMetacallFunction; + meta->d.superdata = nullptr; + flags |= QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + gadgetTypeId = QMetaType::registerType(typeName.constData(), &GadgetTypedDestructor, &GadgetTypedConstructor, sizeof(GadgetType), flags, meta); - QMetaType::registerStreamOperators(gadgetTypeId, &GadgetSaveOperator, &GadgetLoadOperator); + QMetaType::registerStreamOperators(gadgetTypeId, &GadgetSaveOperator, &GadgetLoadOperator); + } else { + gadgetTypeId = QMetaType::registerType(typeName.constData(), + nullptr, + nullptr, + sizeof(GadgetType), + flags, meta); + } trackConnection(gadgetTypeId, connection); QMutexLocker lock(&s_managedTypesMutex); s_managedTypes[gadgetTypeId] = qMakePair(gadgetType, std::shared_ptr<QMetaObject>{meta, [](QMetaObject *ptr){ ::free(ptr); }}); return gadgetTypeId; } -static void registerAllGadgets(IoDeviceBase *connection, QRemoteObjectPackets::GadgetsData &gadgets) +static void registerAllGadgets(IoDeviceBase *connection, Gadgets &gadgets) { while (!gadgets.isEmpty()) - registerGadget(connection, gadgets, gadgets.constBegin().key()); + registerGadgets(connection, gadgets, gadgets.constBegin().key()); +} + +static void deserializeEnum(QDataStream &ds, EnumData &enumData) +{ + ds >> enumData.name; + ds >> enumData.isFlag; + ds >> enumData.isScoped; + ds >> enumData.size; + ds >> enumData.keyCount; + for (quint32 i = 0; i < enumData.keyCount; i++) { + EnumPair pair; + ds >> pair.name; + ds >> pair.value; + enumData.values.push_back(pair); + } } -static void parseGadgets(IoDeviceBase *connection, QDataStream &in, quint32 numGadgets = 1) +static void parseGadgets(IoDeviceBase *connection, QDataStream &in) { - QRemoteObjectPackets::GadgetsData gadgets; + quint32 qtEnums, numGadgets; + in >> qtEnums; // Qt enums - just need registration + for (quint32 i = 0; i < qtEnums; ++i) { + QByteArray enumName; + in >> enumName; + QMetaType t(QMetaType::type(enumName.constData())); + registerEnum(enumName, t.metaObject()); // All Qt enums have default type int + } + in >> numGadgets; + if (numGadgets == 0) + return; + Gadgets gadgets; for (quint32 i = 0; i < numGadgets; ++i) { QByteArray type; in >> type; - quint32 numProperties; + quint32 numProperties, numEnums; in >> numProperties; - auto &properties = gadgets[type]; + auto &properties = gadgets[type].properties; for (quint32 p = 0; p < numProperties; ++p) { - QRemoteObjectPackets::GadgetProperty prop; + GadgetProperty prop; in >> prop.name; in >> prop.type; properties.push_back(prop); } + in >> numEnums; + auto &enums = gadgets[type].enums; + for (quint32 e = 0; e < numEnums; ++e) { + EnumData enumData; + deserializeEnum(in, enumData); + enums.push_back(enumData); + } } registerAllGadgets(connection, gadgets); } @@ -728,43 +880,34 @@ QMetaObject *QRemoteObjectMetaObjectManager::addDynamicType(IoDeviceBase *connec builder.setSuperClass(&QRemoteObjectReplica::staticMetaObject); builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); - QString type; + QString typeString; + QByteArray type; quint32 numEnums = 0; - quint32 numGadgets = 0; quint32 numSignals = 0; quint32 numMethods = 0; quint32 numProperties = 0; - in >> type; - builder.addClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE, type.toLatin1()); - builder.setClassName(type.toLatin1()); + in >> typeString; + type = typeString.toLatin1(); + builder.addClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE, type); + builder.setClassName(type); in >> numEnums; + QVector<quint32> enumSizes(numEnums); for (quint32 i = 0; i < numEnums; ++i) { - QByteArray name; - in >> name; - auto enumBuilder = builder.addEnumerator(name); - bool isFlag; - in >> isFlag; - enumBuilder.setIsFlag(isFlag); - - QByteArray scopeName; - in >> scopeName; // scope - // TODO uncomment this line after https://bugreports.qt.io/browse/QTBUG-64081 is implemented - //enumBuilder.setScope(scopeName); - - int keyCount; - in >> keyCount; - for (int k = 0; k < keyCount; ++k) { - QByteArray key; - int value; - in >> key; - in >> value; - enumBuilder.addKey(key, value); + EnumData enumData; + deserializeEnum(in, enumData); + auto enumBuilder = builder.addEnumerator(enumData.name); + enumBuilder.setIsFlag(enumData.isFlag); + enumBuilder.setIsScoped(enumData.isScoped); + enumSizes[i] = enumData.size; + + for (quint32 k = 0; k < enumData.keyCount; ++k) { + const auto pair = enumData.values.at(k); + enumBuilder.addKey(pair.name, pair.value); } } - in >> numGadgets; - parseGadgets(connection, in, numGadgets); + parseGadgets(connection, in); int curIndex = 0; @@ -812,7 +955,17 @@ QMetaObject *QRemoteObjectMetaObjectManager::addDynamicType(IoDeviceBase *connec } auto meta = builder.toMetaObject(); - dynamicTypes.insert(type, meta); + // Our type likely has enumerations from the inherited base classes, such as the Replica State + // We only want to register the new enumerations, and since we just added them, we know they + // are the last indices. Thus a backwards count seems most efficient. + const int totalEnumCount = meta->enumeratorCount(); + int incrementingIndex = 0; + for (int i = numEnums; i > 0; i--) { + auto const enumMeta = meta->enumerator(totalEnumCount - i); + const QByteArray registeredName = QByteArray(type).append("::").append(enumMeta.name()); + registerEnum(registeredName, meta, enumSizes.at(incrementingIndex++)); + } + dynamicTypes.insert(typeString, meta); return meta; } @@ -1197,7 +1350,7 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj) QDataStream ds(typeInfo.parameters); ds >> rxValue; } - rep->setProperty(propertyIndex, deserializedProperty(rxValue, property)); + rep->setProperty(propertyIndex, decodeVariant(rxValue, property.userType())); } } else { //replica has been deleted, remove from list replicas.remove(rxName); @@ -1220,8 +1373,10 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj) for (int i = 0; i < rxArgs.size(); i++) { if (signal.parameterType(i) == QMetaType::QVariant) param[i + 1] = const_cast<void*>(reinterpret_cast<const void*>(&rxArgs.at(i))); - else + else { + decodeVariant(rxArgs[i], signal.parameterType(i)); param[i + 1] = const_cast<void *>(rxArgs.at(i).data()); + } } } else if (propertyIndex != -1) { param.resize(2); diff --git a/src/remoteobjects/qremoteobjectpacket.cpp b/src/remoteobjects/qremoteobjectpacket.cpp index 283d21b..4b8f77c 100644 --- a/src/remoteobjects/qremoteobjectpacket.cpp +++ b/src/remoteobjects/qremoteobjectpacket.cpp @@ -44,14 +44,77 @@ #include "qremoteobjectpendingcall.h" #include "qremoteobjectsource.h" #include "qremoteobjectsource_p.h" +#include <cstring> //#define QTRO_VERBOSE_PROTOCOL QT_BEGIN_NAMESPACE + +// Add methods so we can use QMetaEnum in a set +// Note for both functions we are skipping string comparisons/hashes. Since the +// metaObjects are the same, we can just use the address of the string. +inline bool operator==(const QMetaEnum e1, const QMetaEnum e2) +{ + return e1.enclosingMetaObject() == e2.enclosingMetaObject() + && e1.name() == e2.name() + && e1.enumName() == e2.enumName() + && e1.scope() == e2.scope(); +} + +inline uint qHash(const QMetaEnum &key, uint seed=0) Q_DECL_NOTHROW +{ + return qHash(key.enclosingMetaObject(), seed) ^ qHash(static_cast<const void *>(key.name()), seed) + ^ qHash(static_cast<const void *>(key.enumName()), seed) ^ qHash(static_cast<const void *>(key.scope()), seed); +} + using namespace QtRemoteObjects; namespace QRemoteObjectPackets { +// QDataStream sends QVariants of custom types by sending their typename, allowing decode +// on the receiving side. For QtRO and enums, this won't work, as the enums have different +// scopes. E.g., the examples have ParentClassSource::MyEnum and ParentClassReplica::MyEnum. +// Dynamic types will be created as ParentClass::MyEnum. So instead, we change the variants +// to integers (encodeVariant) when sending them. On the receive side, the we know the +// types of properties and the signatures for methods, so we can use that information to +// decode the integer variant into an enum variant (via decodeVariant). +const QVariant encodeVariant(const QVariant &value) +{ + if (QMetaType::typeFlags(value.userType()).testFlag(QMetaType::IsEnumeration)) { + auto converted = QVariant(value); + const auto size = QMetaType(value.userType()).sizeOf(); + switch (size) { + case 1: converted.convert(QMetaType::Char); break; + case 2: converted.convert(QMetaType::Short); break; + case 4: converted.convert(QMetaType::Int); break; + // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int) +// case 8: converted.convert(QMetaType::Long); break; // typeId for long from qmetatype.h + default: + qWarning() << "Invalid enum detected" << QMetaType::typeName(value.userType()) << "with size" << size; + converted.convert(QMetaType::Int); + } +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug() << "Converting from enum to integer type" << size << converted << value; +#endif + return converted; + } + return value; +} + +QVariant &decodeVariant(QVariant &value, int type) +{ + if (QMetaType::typeFlags(type).testFlag(QMetaType::IsEnumeration)) { +#ifdef QTRO_VERBOSE_PROTOCOL + QVariant encoded(value); +#endif + value.convert(type); +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug() << "Converting to enum from integer type" << value << encoded; +#endif + } + return value; +} + void serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, int internalIndex) { const int propertyIndex = source->m_api->sourcePropertyIndex(internalIndex); @@ -59,10 +122,6 @@ void serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, i const auto target = source->m_api->isAdapterProperty(internalIndex) ? source->m_adapter : source->m_object; const auto property = target->metaObject()->property(propertyIndex); const QVariant value = property.read(target); - if (property.isEnumType()) { - ds << QVariant::fromValue<qint32>(value.toInt()); - return; - } if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::PointerToQObject)) { auto const childSource = source->m_children.value(internalIndex); auto valueAsPointerToQObject = qvariant_cast<QObject *>(value); @@ -98,17 +157,7 @@ void serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, i return; } } - ds << value; // return original -} - -QVariant deserializedProperty(const QVariant &in, const QMetaProperty &property) -{ - if (property.isEnumType()) { - const qint32 enumValue = in.toInt(); - return QVariant(property.userType(), &enumValue); - } else { - return in; // return original - } + ds << encodeVariant(value); } void serializeHandshakePacket(DataStreamPacket &ds) @@ -185,32 +234,7 @@ void serializeInitDynamicPacket(DataStreamPacket &ds, const QRemoteObjectRootSou ds.finishPacket(); } -static void mergeData(GadgetsData &a, const GadgetsData &b) -{ - for (auto it = b.constBegin(); it != b.constEnd(); ++it) - a[it.key()] = it.value(); -} - -static GadgetsData gadgetData(const QMetaObject *mo) -{ - if (!mo) - return {}; - GadgetsData res; - auto & properties = res[mo->className()]; - const int numProperties = mo->propertyCount(); - for (int i = 0; i < numProperties; ++i) { - const auto property = mo->property(i); - GadgetProperty data; - data.name = property.name(); - data.type = property.typeName(); - if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::IsGadget)) - mergeData(res, gadgetData(QMetaType::metaObjectForType(property.userType()))); - properties.push_back(data); - } - return res; -} - -static ObjectType objectType(const QString &typeName) +static ObjectType getObjectType(const QString &typeName) { if (typeName == QLatin1String("QAbstractItemModelAdapter")) return ObjectType::MODEL; @@ -224,7 +248,51 @@ static ObjectType objectType(const QString &typeName) return ObjectType::CLASS; } -void recurseForGadgets(GadgetsData &gadgets, const QRemoteObjectSourceBase *source) +// Same method as in QVariant.cpp, as it isn't publicly exposed... +static QMetaEnum metaEnumFromType(int type) +{ + QMetaType t(type); + if (t.flags() & QMetaType::IsEnumeration) { + if (const QMetaObject *metaObject = t.metaObject()) { + const char *enumName = QMetaType::typeName(type); + const char *lastColon = std::strrchr(enumName, ':'); + if (lastColon) + enumName = lastColon + 1; + return metaObject->enumerator(metaObject->indexOfEnumerator(enumName)); + } + } + return QMetaEnum(); +} + +static bool checkEnum(int type, QSet<QMetaEnum> &enums) +{ + if (QMetaType::typeFlags(type).testFlag(QMetaType::IsEnumeration)) { + QMetaEnum meta = metaEnumFromType(type); + enums.insert(meta); + return true; + } + return false; +} + +static void recurseMetaobject(const QMetaObject *mo, QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums) +{ + if (!mo || gadgets.contains(mo)) + return; + gadgets.insert(mo); + const int numProperties = mo->propertyCount(); + for (int i = 0; i < numProperties; ++i) { + const auto property = mo->property(i); + if (checkEnum(property.userType(), enums)) + continue; + if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::IsGadget)) + recurseMetaobject(QMetaType::metaObjectForType(property.userType()), gadgets, enums); + } +} + +// A Source may only use a subset of the metaobjects properties/signals/slots, so we only search +// the ones in the API. For nested pointer types, we will have another api to limit the search. +// For nested PODs/enums, we search the entire qobject (using the recurseMetaobject call()). +void recurseForGadgets(QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; @@ -236,12 +304,14 @@ void recurseForGadgets(GadgetsData &gadgets, const QRemoteObjectSourceBase *sour const int params = api->signalParameterCount(si); for (int pi = 0; pi < params; ++pi) { const int type = api->signalParameterType(si, pi); + if (checkEnum(type, enums)) + continue; if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget)) continue; const auto mo = QMetaType::metaObjectForType(type); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; - mergeData(gadgets, gadgetData(mo)); + recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } @@ -250,12 +320,14 @@ void recurseForGadgets(GadgetsData &gadgets, const QRemoteObjectSourceBase *sour const int params = api->methodParameterCount(mi); for (int pi = 0; pi < params; ++pi) { const int type = api->methodParameterType(mi, pi); + if (checkEnum(type, enums)) + continue; if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget)) continue; const auto mo = QMetaType::metaObjectForType(type); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; - mergeData(gadgets, gadgetData(mo)); + recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } @@ -264,34 +336,130 @@ void recurseForGadgets(GadgetsData &gadgets, const QRemoteObjectSourceBase *sour Q_ASSERT(index >= 0); const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object; const auto metaProperty = target->metaObject()->property(index); - if (QMetaType::typeFlags(metaProperty.userType()).testFlag(QMetaType::PointerToQObject)) { - auto const type = objectType(QString::fromLatin1(metaProperty.typeName())); - if (type == ObjectType::CLASS) { + const int type = metaProperty.userType(); + if (checkEnum(type, enums)) + continue; + if (QMetaType::typeFlags(type).testFlag(QMetaType::PointerToQObject)) { + auto const objectType = getObjectType(QString::fromLatin1(metaProperty.typeName())); + if (objectType == ObjectType::CLASS) { auto const childSource = source->m_children.value(pi); if (childSource->m_object) - recurseForGadgets(gadgets, childSource); + recurseForGadgets(gadgets, enums, childSource); } } - const int type = metaProperty.userType(); if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget)) continue; const auto mo = QMetaType::metaObjectForType(type); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; - mergeData(gadgets, gadgetData(mo)); + recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } +static bool checkForEnumsInSource(const QMetaObject *meta, const QRemoteObjectSourceBase *source) +{ + if (source->m_object->inherits(meta->className())) + return true; + for (const auto child : source->m_children) { + if (child->m_object && checkForEnumsInSource(meta, child)) + return true; + } + return false; +} + +static void serializeEnum(QDataStream &ds, const QMetaEnum &enumerator) +{ + ds << QByteArray::fromRawData(enumerator.name(), qstrlen(enumerator.name())); + ds << enumerator.isFlag(); + ds << enumerator.isScoped(); + const auto typeName = QByteArray(enumerator.scope()).append("::").append(enumerator.name()); + quint32 size = QMetaType(QMetaType::type(typeName.constData())).sizeOf(); + ds << size; +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug(" Enum (name = %s, size = %d, isFlag = %s, isScoped = %s):", enumerator.name(), size, enumerator.isFlag() ? "true" : "false", enumerator.isScoped() ? "true" : "false"); +#endif + const int keyCount = enumerator.keyCount(); + ds << keyCount; + for (int k = 0; k < keyCount; ++k) { + ds << QByteArray::fromRawData(enumerator.key(k), qstrlen(enumerator.key(k))); + ds << enumerator.value(k); +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug(" Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k)); +#endif + } +} + +static void serializeGadgets(QDataStream &ds, const QSet<const QMetaObject *> &gadgets, const QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source=nullptr) +{ + // Determine how to handle the enums found + QSet<QMetaEnum> qtEnums; + QSet<const QMetaObject *> dynamicEnumMetaObjects; + for (const auto metaEnum : enums) { + auto const metaObject = metaEnum.enclosingMetaObject(); + if (gadgets.contains(metaObject)) // Part of a gadget will we serialize + continue; + // This checks if the enum is defined in our object heirarchy, in which case it will + // already have been serialized. + if (source && checkForEnumsInSource(metaObject, source->d->root)) + continue; + // qtEnums are enumerations already known by Qt, so we only need register them. + // We don't need to send all of the key/value data. + if (metaObject == qt_getQtMetaObject()) // Are the other Qt metaclasses for enums? + qtEnums.insert(metaEnum); + else + dynamicEnumMetaObjects.insert(metaEnum.enclosingMetaObject()); + } + ds << quint32(qtEnums.size()); + for (const auto metaEnum : qtEnums) { + QByteArray enumName(metaEnum.scope()); + enumName.append("::", 2).append(metaEnum.name()); + ds << enumName; + } + const auto allMetaObjects = gadgets + dynamicEnumMetaObjects; + ds << quint32(allMetaObjects.size()); +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug() << " Found" << gadgets.size() << "gadget/pod and" << (allMetaObjects.size() - gadgets.size()) << "enum types"; + int i = 0; +#endif + // There isn't an easy way to update a metaobject incrementally, so we + // send all of the metaobject's enums, but no properties, when an external + // enum is requested. + for (auto const meta : allMetaObjects) { + ds << QByteArray::fromRawData(meta->className(), qstrlen(meta->className())); + int propertyCount = gadgets.contains(meta) ? meta->propertyCount() : 0; + ds << quint32(propertyCount); +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug(" Gadget %d (name = %s, # properties = %d, # enums = %d):", i++, meta->className(), propertyCount, meta->enumeratorCount()); +#endif + for (int j = 0; j < propertyCount; j++) { + auto prop = meta->property(j); +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug(" Data member %d (name = %s, type = %s):", j, prop.name(), prop.typeName()); +#endif + ds << QByteArray::fromRawData(prop.name(), qstrlen(prop.name())); + ds << QByteArray::fromRawData(prop.typeName(), qstrlen(prop.typeName())); + } + int enumCount = meta->enumeratorCount(); + ds << enumCount; + for (int j = 0; j < enumCount; j++) { + auto const enumMeta = meta->enumerator(j); + serializeEnum(ds, enumMeta); + } + } +} + void serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; - bool dynamic = source->m_api->isDynamic(); - const QByteArray classname(source->m_api->typeName().toLatin1()); - const QByteArray sourcename = QByteArray(classname).append("Source"); - auto replace = [&classname, &sourcename, dynamic](QByteArray &name) { - if (!dynamic) // Compiled classes likely have <ClassNameSource> that should be <ClassName> - name.replace(sourcename, classname); + const QByteArray desiredClassName(api->typeName().toLatin1()); + const QByteArray originalClassName = api->className(); + // The dynamic class will be called typeName on the receiving side of this definition + // However, there are types like enums that have the QObject's class name. Replace() + // will convert a parameter such as "ParentClassSource::MyEnum" to "ParentClass::MyEnum" + // so the type can be properly resolved and registered. + auto replace = [&originalClassName, &desiredClassName](QByteArray &name) { + name.replace(originalClassName, desiredClassName); }; ds << source->m_api->typeName(); @@ -309,47 +477,16 @@ void serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source) for (int i = 0; i < numEnums; ++i) { auto enumerator = metaObject->enumerator(api->sourceEnumIndex(i)); Q_ASSERT(enumerator.isValid()); - ds << enumerator.name(); - ds << enumerator.isFlag(); - ds << enumerator.scope(); -#ifdef QTRO_VERBOSE_PROTOCOL - qDebug(" Enum %d (name = %s, isFlag = %s, scope = %s):", i, enumerator.name(), enumerator.isFlag() ? "true" : "false", enumerator.scope()); -#endif - const int keyCount = enumerator.keyCount(); - ds << keyCount; - for (int k = 0; k < keyCount; ++k) { - ds << enumerator.key(k); - ds << enumerator.value(k); -#ifdef QTRO_VERBOSE_PROTOCOL - qDebug(" Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k)); -#endif - } + serializeEnum(ds, enumerator); } if (source->d->isDynamic) { - GadgetsData gadgets; - recurseForGadgets(gadgets, source); - ds << quint32(gadgets.size()); -#ifdef QTRO_VERBOSE_PROTOCOL - qDebug() << " Found" << gadgets.size() << "gadget/pod types"; - int i = 0, j = 0; -#endif - for (auto it = gadgets.constBegin(); it != gadgets.constEnd(); ++it) { - ds << it.key(); - ds << quint32(it.value().size()); -#ifdef QTRO_VERBOSE_PROTOCOL - qDebug(" Gadget %d (name = %s):", i++, it.key().constData()); -#endif - for (const auto &prop : qAsConst(it.value())) { -#ifdef QTRO_VERBOSE_PROTOCOL - qDebug(" Data member %d (name = %s, type = %s):", j++, prop.name.constData(), prop.type.constData()); -#endif - ds << prop.name; - ds << prop.type; - } - } + QSet<const QMetaObject *> gadgets; + QSet<QMetaEnum> enums; + recurseForGadgets(gadgets, enums, source); + serializeGadgets(ds, gadgets, enums, source); } else - ds << quint32(0); + ds << quint32(0) << quint32(0); // qtEnums, numGadgets const int numSignals = api->signalCount(); ds << quint32(numSignals); //Number of signals @@ -370,11 +507,15 @@ void serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source) for (int i = 0; i < numMethods; ++i) { const int index = api->sourceMethodIndex(i); Q_ASSERT(index >= 0); + auto signature = api->methodSignature(i); + replace(signature); + auto typeName = api->typeName(i); + replace(typeName); #ifdef QTRO_VERBOSE_PROTOCOL - qDebug() << " Slot" << i << "(signature =" << api->methodSignature(i) << "parameter names =" << api->methodParameterNames(i) << "return type =" << api->typeName(i) << ")"; + qDebug() << " Slot" << i << "(signature =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")"; #endif - ds << api->methodSignature(i); - ds << api->typeName(i); + ds << signature; + ds << typeName; ds << api->methodParameterNames(i); } @@ -391,10 +532,10 @@ void serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source) qDebug() << " Property" << i << "name =" << metaProperty.name(); #endif if (QMetaType::typeFlags(metaProperty.userType()).testFlag(QMetaType::PointerToQObject)) { - auto type = objectType(QLatin1String(metaProperty.typeName())); - ds << (type == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); + auto objectType = getObjectType(QLatin1String(metaProperty.typeName())); + ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); #ifdef QTRO_VERBOSE_PROTOCOL - qDebug() << " Type:" << (type == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); + qDebug() << " Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); #endif } else { ds << metaProperty.typeName(); @@ -447,12 +588,8 @@ void serializeInvokePacket(DataStreamPacket &ds, const QString &name, int call, ds << index; ds << (quint32)args.size(); - foreach (const auto &arg, args) { - if (QMetaType::typeFlags(arg.userType()).testFlag(QMetaType::IsEnumeration)) - ds << QVariant::fromValue<qint32>(arg.toInt()); - else - ds << arg; - } + foreach (const auto &arg, args) + ds << encodeVariant(arg); ds << serialId; ds << propertyIndex; @@ -530,7 +667,7 @@ void serializePongPacket(DataStreamPacket &ds, const QString &name) QRO_::QRO_(QRemoteObjectSourceBase *source) : name(source->name()) , typeName(source->m_api->typeName()) - , type(source->m_adapter ? ObjectType::MODEL : objectType(typeName)) + , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName)) , isNull(source->m_object == nullptr) , classDefinition() , parameters() @@ -543,7 +680,8 @@ QRO_::QRO_(const QVariant &value) auto meta = QMetaType::metaObjectForType(value.userType()); QDataStream out(&classDefinition, QIODevice::WriteOnly); const int numProperties = meta->propertyCount(); - const auto typeName = QByteArray(QMetaType::typeName(value.userType())); + const auto typeName = QByteArray::fromRawData(QMetaType::typeName(value.userType()), qstrlen(QMetaType::typeName(value.userType()))); + out << quint32(0) << quint32(1); out << typeName; out << numProperties; #ifdef QTRO_VERBOSE_PROTOCOL @@ -554,8 +692,8 @@ QRO_::QRO_(const QVariant &value) #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Data member %d (name = %s, type = %s):", i, property.name(), property.typeName()); #endif - out << property.name(); - out << property.typeName(); + out << QByteArray::fromRawData(property.name(), qstrlen(property.name())); + out << QByteArray::fromRawData(property.typeName(), qstrlen(property.typeName())); } QDataStream ds(¶meters, QIODevice::WriteOnly); ds << value; @@ -567,8 +705,7 @@ QRO_::QRO_(const QVariant &value) QDataStream &operator<<(QDataStream &stream, const QRO_ &info) { stream << info.name << info.typeName << (quint8)(info.type) << info.classDefinition << info.isNull; - qCDebug(QT_REMOTEOBJECT) << "Serializing QRO_" << info.name << info.typeName << (info.type == ObjectType::CLASS ? "Class" : info.type == ObjectType::MODEL ? "Model" : "Gadget") - << (info.isNull ? "nullptr" : "valid pointer") << (info.classDefinition.isEmpty() ? "no definitions" : "with definitions"); + qCDebug(QT_REMOTEOBJECT) << "Serializing " << info; // info.parameters will be filled in by serializeProperty return stream; } @@ -578,8 +715,7 @@ QDataStream &operator>>(QDataStream &stream, QRO_ &info) quint8 tmpType; stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull; info.type = static_cast<ObjectType>(tmpType); - qCDebug(QT_REMOTEOBJECT) << "Deserializing QRO_" << info.name << info.typeName << (info.isNull ? "nullptr" : "valid pointer") - << (info.classDefinition.isEmpty() ? "no definitions" : "with definitions"); + qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info; if (!info.isNull) stream >> info.parameters; return stream; diff --git a/src/remoteobjects/qremoteobjectpacket_p.h b/src/remoteobjects/qremoteobjectpacket_p.h index 542f998..612fabf 100644 --- a/src/remoteobjects/qremoteobjectpacket_p.h +++ b/src/remoteobjects/qremoteobjectpacket_p.h @@ -72,6 +72,8 @@ class QRemoteObjectRootSource; namespace QRemoteObjectPackets { +Q_NAMESPACE + class DataStreamPacket; struct ObjectInfo @@ -100,6 +102,7 @@ inline QDataStream& operator>>(QDataStream &stream, ObjectInfo &info) typedef QVector<ObjectInfo> ObjectInfoList; enum class ObjectType : quint8 { CLASS, MODEL, GADGET }; +Q_ENUM_NS(ObjectType) // Use a short name, as QVariant::save writes the name every time a qvariant of // this type is serialized @@ -118,9 +121,9 @@ public: inline QDebug operator<<(QDebug dbg, const QRO_ &info) { - dbg.nospace() << "QRO_(name: " << info.name << "typeName: " << info.typeName << "type: " << (info.type == ObjectType::CLASS ? "Class" : "Model") - << ", valid: " << (info.isNull ? "true" : "false") - << ", paremeters: {" << info.parameters <<")"; + dbg.nospace() << "QRO_(name: " << info.name << ", typeName: " << info.typeName << ", type: " << info.type + << ", valid: " << (info.isNull ? "true" : "false") << ", paremeters: {" << info.parameters <<")" + << (info.classDefinition.isEmpty() ? " no definitions)" : " with definitions)"); return dbg.space(); } @@ -131,13 +134,6 @@ QDataStream& operator>>(QDataStream &stream, QRO_ &info); void serializeObjectListPacket(DataStreamPacket&, const ObjectInfoList&); void deserializeObjectListPacket(QDataStream&, ObjectInfoList&); -struct GadgetProperty { - QByteArray name; - QByteArray type; -}; - -using GadgetsData = QHash<QByteArray, QVector<GadgetProperty>>; - //Helper class for creating a QByteArray from a QRemoteObjectPacket class DataStreamPacket : public QDataStream { @@ -172,8 +168,10 @@ private: Q_DISABLE_COPY(DataStreamPacket) }; +const QVariant encodeVariant(const QVariant &value); +QVariant &decodeVariant(QVariant &value, int type); + void serializeProperty(QDataStream &, const QRemoteObjectSourceBase *source, int internalIndex); -QVariant deserializedProperty(const QVariant &in, const QMetaProperty &property); void serializeHandshakePacket(DataStreamPacket &); void serializeInitPacket(DataStreamPacket &, const QRemoteObjectRootSource*); diff --git a/src/remoteobjects/qremoteobjectreplica.cpp b/src/remoteobjects/qremoteobjectreplica.cpp index c663efd..6536dc8 100644 --- a/src/remoteobjects/qremoteobjectreplica.cpp +++ b/src/remoteobjects/qremoteobjectreplica.cpp @@ -196,7 +196,7 @@ QVector<int> QConnectedReplicaImplementation::childIndices() const return m_childIndices; } -void QConnectedReplicaImplementation::initialize(const QVariantList &values) +void QConnectedReplicaImplementation::initialize(QVariantList &values) { qCDebug(QT_REMOTEOBJECT) << "initialize()" << m_propertyStorage.size(); const int nParam = values.size(); @@ -207,7 +207,7 @@ void QConnectedReplicaImplementation::initialize(const QVariantList &values) changedProperties[i] = -1; if (m_propertyStorage[i] != values.at(i)) { const QMetaProperty property = m_metaObject->property(i+offset); - m_propertyStorage[i] = QRemoteObjectPackets::deserializedProperty(values.at(i), property); + m_propertyStorage[i] = QRemoteObjectPackets::decodeVariant(values[i], property.userType()); changedProperties[i] = i; } qCDebug(QT_REMOTEOBJECT) << "SETPROPERTY" << i << m_metaObject->property(i+offset).name() << values.at(i).typeName() << values.at(i).toString(); diff --git a/src/remoteobjects/qremoteobjectreplica_p.h b/src/remoteobjects/qremoteobjectreplica_p.h index e724dae..79a07cf 100644 --- a/src/remoteobjects/qremoteobjectreplica_p.h +++ b/src/remoteobjects/qremoteobjectreplica_p.h @@ -158,7 +158,7 @@ public: bool isInitialized() const override; bool waitForSource(int timeout) override; QVector<int> childIndices() const; - void initialize(const QVariantList &values); + void initialize(QVariantList &values); void configurePrivate(QRemoteObjectReplica *) override; void requestRemoteObjectSource(); bool sendCommand(); diff --git a/src/remoteobjects/qremoteobjectsource.cpp b/src/remoteobjects/qremoteobjectsource.cpp index 8b8b280..3ddec27 100644 --- a/src/remoteobjects/qremoteobjectsource.cpp +++ b/src/remoteobjects/qremoteobjectsource.cpp @@ -146,7 +146,7 @@ QRemoteObjectSource::QRemoteObjectSource(QObject *obj, Private *d, const SourceA QRemoteObjectRootSource::QRemoteObjectRootSource(QObject *obj, const SourceApiMap *api, QObject *adapter, QRemoteObjectSourceIo *sourceIo) - : QRemoteObjectSourceBase(obj, new Private(sourceIo), api, adapter) + : QRemoteObjectSourceBase(obj, new Private(sourceIo, this), api, adapter) , m_name(api->name()) { d->m_sourceIo->registerSource(this); @@ -365,7 +365,9 @@ void QRemoteObjectSourceBase::handleMetaCall(int index, QMetaObject::Call call, } qCDebug(QT_REMOTEOBJECT) << "# Listeners" << d->m_listeners.length(); - qCDebug(QT_REMOTEOBJECT) << "Invoke args:" << m_object << call << index << *marshalArgs(index, a); + qCDebug(QT_REMOTEOBJECT) << "Invoke args:" << m_object + << (call == 0 ? QLatin1String("InvokeMetaMethod") : QStringLiteral("Non-invoked call: %d").arg(call)) + << m_api->signalSignature(index) << *marshalArgs(index, a); serializeInvokePacket(d->m_packet, name(), call, index, *marshalArgs(index, a), -1, propertyIndex); d->m_packet.baseAddress = 0; diff --git a/src/remoteobjects/qremoteobjectsource.h b/src/remoteobjects/qremoteobjectsource.h index f2993bd..61346df 100644 --- a/src/remoteobjects/qremoteobjectsource.h +++ b/src/remoteobjects/qremoteobjectsource.h @@ -99,6 +99,56 @@ static inline void qtro_method_test(Func1, Func2) "Return types are not compatible."); } +// The stringData, methodMatch and QMetaObjectPrivate methods are modified versions of the code +// from qmetaobject_p.h/qmetaobject.cpp. The modifications are based on our custom need to match +// a method name that comes from the .rep file. +// The QMetaObjectPrivate struct should only have members appended to maintain binary compatibility, +// so we should be fine with only the listed version with the fields we use. +inline const QByteArray apiStringData(const QMetaObject *mo, int index) +{ + const QByteArrayDataPtr data = { const_cast<QByteArrayData*>(&mo->d.stringdata[index]) }; + return data; +} + +inline bool apiMethodMatch(const QMetaObject *m, int handle, + const QByteArray &name, int argc, + const int *types) +{ + if (int(m->d.data[handle + 1]) != argc) + return false; + if (apiStringData(m, m->d.data[handle]) != name) + return false; + int paramsIndex = m->d.data[handle + 2] + 1; + for (int i = 0; i < argc; ++i) { + uint typeInfo = m->d.data[paramsIndex + i]; + if (typeInfo & 0x80000000) { // Custom/named type, compare names + const char *t = QMetaType::typeName(types[i]); + const auto type = QByteArray::fromRawData(t, qstrlen(t)); + if (type != apiStringData(m, typeInfo & 0x7FFFFFFF)) + return false; + } else if (types[i] != int(typeInfo)) + return false; + } + return true; +} + +struct QMetaObjectPrivate +{ + // revision 7 is Qt 5.0 everything lower is not supported + // revision 8 is Qt 5.12: It adds the enum name to QMetaEnum + enum { OutputRevision = 8 }; // Used by moc, qmetaobjectbuilder and qdbus + + int revision; + int className; + int classInfoCount, classInfoData; + int methodCount, methodData; + int propertyCount, propertyData; + int enumeratorCount, enumeratorData; + int constructorCount, constructorData; + int flags; + int signalCount; +}; + template <class ObjectType, typename Func1, typename Func2> static inline int qtro_method_index(Func1, Func2, const char *methodName, int *count, int const **types) { @@ -114,7 +164,33 @@ static inline int qtro_method_index(Func1, Func2, const char *methodName, int *c "Return types are not compatible."); *count = Type2::ArgumentCount; *types = QtPrivate::ConnectionTypes<typename Type2::Arguments>::types(); - return ObjectType::staticMetaObject.indexOfMethod(methodName); + + int result = ObjectType::staticMetaObject.indexOfMethod(methodName); + if (result >= 0) + return result; + // We can have issues, specifically with enums, since the compiler can infer the class. Since + // indexOfMethod() is doing string comparisons for registered types, "MyEnum" and "MyClass::MyEnum" + // won't match. + // Below is similar to QMetaObject->indexOfMethod, but template magic has already matched parameter + // types, so we need to find a match for the API method name + parameters. Neither approach works + // 100%, as the below code doesn't match a parameter of type "size_t" (which the template match + // identifies as "ulong"). These subtleties can cause the below string comparison fails. + // There is no known case that would fail both methods. + // TODO: is there a way to make this a constexpr so a failure is detected at compile time? + int nameLength = strchr(methodName, '(') - methodName; + const auto name = QByteArray::fromRawData(methodName, nameLength); + for (const QMetaObject *m = &ObjectType::staticMetaObject; m; m = m->d.superdata) { + const auto priv = reinterpret_cast<const QMetaObjectPrivate*>(m->d.data); + int i = (priv->methodCount - 1); + const int end = priv->signalCount; + for (; i >= end; --i) { + int handle = priv->methodData + 5*i; + if (apiMethodMatch(m, handle, name, *count, *types)) + return i + m->methodOffset(); + } + } + qWarning() << "No matching method for" << methodName << "in the provided metaclass" << ObjectType::staticMetaObject.className(); + return -1; } template <class ObjectType> @@ -146,6 +222,7 @@ public: virtual ~SourceApiMap() {} virtual QString name() const = 0; virtual QString typeName() const = 0; + virtual QByteArray className() const { return typeName().toLatin1().append("Source"); } virtual int enumCount() const = 0; virtual int propertyCount() const = 0; virtual int signalCount() const = 0; diff --git a/src/remoteobjects/qremoteobjectsource_p.h b/src/remoteobjects/qremoteobjectsource_p.h index 0fdd3c8..602c89d 100644 --- a/src/remoteobjects/qremoteobjectsource_p.h +++ b/src/remoteobjects/qremoteobjectsource_p.h @@ -84,7 +84,7 @@ public: QByteArray m_objectChecksum; QMap<int, QPointer<QRemoteObjectSourceBase>> m_children; struct Private { - Private(QRemoteObjectSourceIo *io) : m_sourceIo(io), isDynamic(false) {} + Private(QRemoteObjectSourceIo *io, QRemoteObjectRootSource *root) : m_sourceIo(io), isDynamic(false), root(root) {} QRemoteObjectSourceIo *m_sourceIo; QVector<IoDeviceBase*> m_listeners; QRemoteObjectPackets::DataStreamPacket m_packet; @@ -92,6 +92,7 @@ public: // Types needed during recursively sending a root to a new listener QSet<QString> sentTypes; bool isDynamic; + QRemoteObjectRootSource *root; }; Private *d; static const int qobjectPropertyOffset; @@ -134,6 +135,7 @@ public: ~DynamicApiMap() override {} QString name() const override { return m_name; } QString typeName() const override { return m_typeName; } + QByteArray className() const override { return QByteArray(m_metaObject->className()); } int enumCount() const override { return m_enumCount; } int propertyCount() const override { return m_properties.size(); } int signalCount() const override { return m_signals.size(); } diff --git a/src/remoteobjects/qremoteobjectsourceio.cpp b/src/remoteobjects/qremoteobjectsourceio.cpp index bde010b..189e5f4 100644 --- a/src/remoteobjects/qremoteobjectsourceio.cpp +++ b/src/remoteobjects/qremoteobjectsourceio.cpp @@ -235,8 +235,13 @@ void QRemoteObjectSourceIo::onServerRead(QObject *conn) } if (source->m_api->isAdapterMethod(index)) qRODebug(this) << "Adapter (method) Invoke-->" << m_rxName << source->m_adapter->metaObject()->method(resolvedIndex).name(); - else - qRODebug(this) << "Source (method) Invoke-->" << m_rxName << source->m_object->metaObject()->method(resolvedIndex).name(); + else { + qRODebug(this) << "Source (method) Invoke-->" << m_rxName << source->m_object->metaObject()->method(resolvedIndex).methodSignature(); + auto method = source->m_object->metaObject()->method(resolvedIndex); + const int parameterCount = method.parameterCount(); + for (int i = 0; i < parameterCount; i++) + decodeVariant(m_rxArgs[i], method.parameterType(i)); + } int typeId = QMetaType::type(source->m_api->typeName(index).constData()); if (!QMetaType(typeId).sizeOf()) typeId = QVariant::Invalid; @@ -254,13 +259,13 @@ void QRemoteObjectSourceIo::onServerRead(QObject *conn) QRemoteObjectPendingCallWatcher *watcher = new QRemoteObjectPendingCallWatcher(call, connection); QObject::connect(watcher, &QRemoteObjectPendingCallWatcher::finished, connection, [this, serialId, connection, watcher]() { if (watcher->error() == QRemoteObjectPendingCall::NoError) { - serializeInvokeReplyPacket(this->m_packet, this->m_rxName, serialId, watcher->returnValue()); + serializeInvokeReplyPacket(this->m_packet, this->m_rxName, serialId, encodeVariant(watcher->returnValue())); connection->write(m_packet.array, m_packet.size); } watcher->deleteLater(); }); } else { - serializeInvokeReplyPacket(m_packet, m_rxName, serialId, returnValue); + serializeInvokeReplyPacket(m_packet, m_rxName, serialId, encodeVariant(returnValue)); connection->write(m_packet.array, m_packet.size); } } diff --git a/tests/auto/integration/tst_integration.cpp b/tests/auto/integration/tst_integration.cpp index af5cba0..7f39381 100644 --- a/tests/auto/integration/tst_integration.cpp +++ b/tests/auto/integration/tst_integration.cpp @@ -345,8 +345,9 @@ private slots: // set property on the replica (test property change packet) { QSignalSpy spy(tc_rep.data(), SIGNAL(classEnumChanged(TestClassReplica::ClassEnum))); + QVERIFY(spy.isValid()); tc_rep->pushClassEnum(TestClassReplica::Two); - QVERIFY(spy.wait()); + QVERIFY(spy.count() || spy.wait()); QCOMPARE((qint32)tc.classEnum(), (qint32)tc_rep->classEnum()); } @@ -405,7 +406,7 @@ private slots: QSignalSpy spy(tc_rep.data(), SIGNAL(classEnumChanged(TestClassReplica::ClassEnum))); bool res = property.write(tc_repDynamic.data(), TestClassReplica::Two); QVERIFY(!res); - int methodIndex = metaObject->indexOfMethod("pushClassEnum(ClassEnum)"); + int methodIndex = metaObject->indexOfMethod("pushClassEnum(TestClassReplica::ClassEnum)"); QVERIFY(methodIndex >= 0); QMetaMethod method = metaObject->method(methodIndex); QVERIFY(method.isValid()); diff --git a/tests/auto/integration_external/client/main.cpp b/tests/auto/integration_external/client/main.cpp index 05f7f59..bc53333 100644 --- a/tests/auto/integration_external/client/main.cpp +++ b/tests/auto/integration_external/client/main.cpp @@ -129,7 +129,7 @@ private Q_SLOTS: QCOMPARE(simm.parameterType(2), int(QMetaType::QString)); } - int slotIdx = mo->indexOfSlot("testEnumParamsInSlots(Enum1,bool,int)"); + int slotIdx = mo->indexOfSlot("testEnumParamsInSlots(MyInterfaceReplica::Enum1,bool,int)"); QVERIFY(slotIdx != -1); auto slmm = mo->method(slotIdx); { @@ -143,7 +143,7 @@ private Q_SLOTS: int enumVal = 0; mo->invokeMethod(rep.data(), "testEnumParamsInSlots", - QGenericArgument("Enum1", &enumVal), + QGenericArgument("MyInterfaceReplica::Enum1", &enumVal), Q_ARG(bool, true), Q_ARG(int, 1234)); int enumIdx = mo->indexOfProperty("enum1"); diff --git a/tests/auto/integration_multiprocess/client/main.cpp b/tests/auto/integration_multiprocess/client/main.cpp index b60a48a..87f2ad8 100644 --- a/tests/auto/integration_multiprocess/client/main.cpp +++ b/tests/auto/integration_multiprocess/client/main.cpp @@ -112,7 +112,7 @@ private Q_SLOTS: QCOMPARE(simm.parameterType(2), int(QMetaType::QString)); } - int slotIdx = mo->indexOfSlot("testEnumParamsInSlots(Enum1,bool,int)"); + int slotIdx = mo->indexOfSlot("testEnumParamsInSlots(MyInterfaceReplica::Enum1,bool,int)"); QVERIFY(slotIdx != -1); auto slmm = mo->method(slotIdx); { @@ -126,7 +126,7 @@ private Q_SLOTS: int enumVal = 0; mo->invokeMethod(rep.data(), "testEnumParamsInSlots", - QGenericArgument("Enum1", &enumVal), + QGenericArgument("MyInterfaceReplica::Enum1", &enumVal), Q_ARG(bool, true), Q_ARG(int, 1234)); int enumIdx = mo->indexOfProperty("enum1"); diff --git a/tests/auto/proxy_multiprocess/client/client.pro b/tests/auto/proxy_multiprocess/client/client.pro index d6c31db..7a181a2 100644 --- a/tests/auto/proxy_multiprocess/client/client.pro +++ b/tests/auto/proxy_multiprocess/client/client.pro @@ -12,6 +12,7 @@ REPC_REPLICA = ../subclass.rep SOURCES += main.cpp \ HEADERS += \ - ../shared.h + ../shared.h \ + ../namespace.h INCLUDEPATH += $$PWD diff --git a/tests/auto/proxy_multiprocess/client/main.cpp b/tests/auto/proxy_multiprocess/client/main.cpp index f37821f..19ba46f 100644 --- a/tests/auto/proxy_multiprocess/client/main.cpp +++ b/tests/auto/proxy_multiprocess/client/main.cpp @@ -60,28 +60,49 @@ private Q_SLOTS: QCOMPARE(m_rep->subClass()->i(), initialI); QVERIFY(m_rep->tracks() != nullptr); QVERIFY(tracksSpy.count() || tracksSpy.wait()); - QCOMPARE(m_rep->myEnum(), QVariant::fromValue(ParentClassReplica::bar)); + QCOMPARE(m_rep->myEnum(), ParentClassReplica::bar); + QCOMPARE(m_rep->date(), Qt::SystemLocaleShortDate); + QCOMPARE(m_rep->nsEnum(), NS::Bravo); + QCOMPARE(m_rep->ns2Enum(), NS2::NamespaceEnum::Bravo); QCOMPARE(m_rep->variant(), QVariant::fromValue(42.0f)); } else { QVERIFY(m_rep->subClass() == nullptr); QVERIFY(m_rep->tracks() == nullptr); - QCOMPARE(m_rep->myEnum(), QVariant::fromValue(ParentClassReplica::foo)); + QCOMPARE(m_rep->myEnum(), ParentClassReplica::foo); + QCOMPARE(m_rep->date(), Qt::ISODate); + QCOMPARE(m_rep->nsEnum(), NS::Alpha); + QCOMPARE(m_rep->ns2Enum(), NS2::NamespaceEnum::Alpha); QCOMPARE(m_rep->variant(), QVariant()); } + QPoint p(1, 2); + auto enumReply = m_rep->enumSlot(p, ParentClassReplica::bar); + QVERIFY(enumReply.waitForFinished()); + QCOMPARE(enumReply.error(), QRemoteObjectPendingCall::NoError); + QCOMPARE(enumReply.returnValue(), QVariant::fromValue(ParentClassReplica::foobar)); + qDebug() << "Verified expected initial states, sending start."; + QSignalSpy enumSpy(m_rep.data(), &ParentClassReplica::enum2); + QSignalSpy advanceSpy(m_rep.data(), SIGNAL(advance())); auto reply = m_rep->start(); QVERIFY(reply.waitForFinished()); QVERIFY(reply.error() == QRemoteObjectPendingCall::NoError); QCOMPARE(reply.returnValue(), QVariant::fromValue(true)); + QVERIFY(enumSpy.wait()); + QCOMPARE(enumSpy.count(), 1); + const auto arguments = enumSpy.takeFirst(); + QCOMPARE(arguments.at(0), QVariant::fromValue(ParentClassReplica::foo)); + QCOMPARE(arguments.at(1), QVariant::fromValue(ParentClassReplica::bar)); - QSignalSpy advanceSpy(m_rep.data(), SIGNAL(advance())); - QVERIFY(advanceSpy.wait()); + QVERIFY(advanceSpy.count() || advanceSpy.wait()); QVERIFY(m_rep->subClass() != nullptr); QCOMPARE(m_rep->subClass()->myPOD(), updatedValue); QCOMPARE(m_rep->subClass()->i(), updatedI); QVERIFY(m_rep->tracks() != nullptr); - QCOMPARE(m_rep->myEnum(), QVariant::fromValue(ParentClassReplica::foobar)); + QCOMPARE(m_rep->myEnum(), ParentClassReplica::foobar); + QCOMPARE(m_rep->date(), Qt::SystemLocaleLongDate); + QCOMPARE(m_rep->nsEnum(), NS::Charlie); + QCOMPARE(m_rep->ns2Enum(), NS2::NamespaceEnum::Charlie); QCOMPARE(m_rep->variant(), QVariant::fromValue(podValue)); qDebug() << "Verified expected final states, cleaning up."; } diff --git a/tests/auto/proxy_multiprocess/namespace.h b/tests/auto/proxy_multiprocess/namespace.h new file mode 100644 index 0000000..2d06e67 --- /dev/null +++ b/tests/auto/proxy_multiprocess/namespace.h @@ -0,0 +1,15 @@ +#include <QMetaType> + +namespace NS +{ + Q_NAMESPACE + enum NamespaceEnum { Alpha=1, Bravo, Charlie }; + Q_ENUM_NS(NamespaceEnum) +} + +namespace NS2 +{ + Q_NAMESPACE + enum class NamespaceEnum : quint8 { Alpha=1, Bravo, Charlie }; + Q_ENUM_NS(NamespaceEnum) +} diff --git a/tests/auto/proxy_multiprocess/proxy_multiprocess.pro b/tests/auto/proxy_multiprocess/proxy_multiprocess.pro index 075bc00..6bd7af7 100644 --- a/tests/auto/proxy_multiprocess/proxy_multiprocess.pro +++ b/tests/auto/proxy_multiprocess/proxy_multiprocess.pro @@ -1,2 +1,5 @@ TEMPLATE = subdirs SUBDIRS = client server proxy tst + +OTHER_FILES += shared.h \ + namespace.h diff --git a/tests/auto/proxy_multiprocess/server/main.cpp b/tests/auto/proxy_multiprocess/server/main.cpp index 2e6895b..1cd005d 100644 --- a/tests/auto/proxy_multiprocess/server/main.cpp +++ b/tests/auto/proxy_multiprocess/server/main.cpp @@ -56,6 +56,9 @@ private Q_SLOTS: parent.setSubClass(&subclass); parent.setTracks(&model); parent.setMyEnum(ParentClassSource::bar); + parent.setDate(Qt::SystemLocaleShortDate); + parent.setNsEnum(NS::Bravo); + parent.setNs2Enum(NS2::NamespaceEnum::Bravo); parent.setVariant(QVariant::fromValue(42.0f)); } @@ -82,7 +85,11 @@ private Q_SLOTS: if (objectMode == QLatin1Literal("NullPointer")) parent.setTracks(&model); parent.setMyEnum(ParentClassSource::foobar); + parent.setDate(Qt::SystemLocaleLongDate); + parent.setNsEnum(NS::Charlie); + parent.setNs2Enum(NS2::NamespaceEnum::Charlie); parent.setVariant(QVariant::fromValue(podValue)); + emit parent.enum2(ParentClassSource::foo, ParentClassSource::bar); emit parent.advance(); diff --git a/tests/auto/proxy_multiprocess/server/mytestserver.cpp b/tests/auto/proxy_multiprocess/server/mytestserver.cpp index b0c75ab..e55739d 100644 --- a/tests/auto/proxy_multiprocess/server/mytestserver.cpp +++ b/tests/auto/proxy_multiprocess/server/mytestserver.cpp @@ -29,7 +29,6 @@ #include <qdebug.h> #include "mytestserver.h" -#include "rep_subclass_source.h" MyTestServer::MyTestServer(QObject *parent) : ParentClassSimpleSource(parent) @@ -53,3 +52,16 @@ bool MyTestServer::quit() emit quitApp(); return true; } + +ParentClassSource::MyEnum MyTestServer::enumSlot(QPoint p, MyEnum myEnum) +{ + Q_UNUSED(p) + Q_UNUSED(myEnum) + return ParentClassSource::foobar; +} + +Qt::DateFormat MyTestServer::dateSlot(Qt::DateFormat date) +{ + Q_UNUSED(date) + return Qt::SystemLocaleDate; +} diff --git a/tests/auto/proxy_multiprocess/server/mytestserver.h b/tests/auto/proxy_multiprocess/server/mytestserver.h index df044c1..8a4d09b 100644 --- a/tests/auto/proxy_multiprocess/server/mytestserver.h +++ b/tests/auto/proxy_multiprocess/server/mytestserver.h @@ -47,6 +47,8 @@ public: public Q_SLOTS: bool start() override; bool quit() override; + MyEnum enumSlot(QPoint p, MyEnum myEnum) override; + Qt::DateFormat dateSlot(Qt::DateFormat date) override; Q_SIGNALS: void quitApp(); diff --git a/tests/auto/proxy_multiprocess/server/server.pro b/tests/auto/proxy_multiprocess/server/server.pro index 24c9a13..02ca498 100644 --- a/tests/auto/proxy_multiprocess/server/server.pro +++ b/tests/auto/proxy_multiprocess/server/server.pro @@ -14,6 +14,7 @@ SOURCES += main.cpp \ HEADERS += \ ../shared.h \ + ../namespace.h \ mytestserver.h \ $$OUT_PWD/rep_subclass_source.h diff --git a/tests/auto/proxy_multiprocess/subclass.rep b/tests/auto/proxy_multiprocess/subclass.rep index 7055944..2538b88 100644 --- a/tests/auto/proxy_multiprocess/subclass.rep +++ b/tests/auto/proxy_multiprocess/subclass.rep @@ -1,3 +1,10 @@ +#include <QPoint> +#include "../namespace.h" + +USE_ENUM(Qt::DateFormat) +USE_ENUM(NS::NamespaceEnum) +USE_ENUM(NS2::NamespaceEnum) + POD MyPOD(int i, float f, QString s) POD VariantPOD(int i, int j) @@ -9,17 +16,22 @@ class SubClass class ParentClass { - // We need several Enums to test dynamic registration - ENUM MyEnumProp {foo=1, bar=3, foobar=6} - ENUM MyEnumSignal {a=1, b, c} + ENUM MyEnum {foo=1, bar=3, foobar=6} PROP(bool started = false) - PROP(MyEnumProp myEnum=foo) + PROP(MyEnum myEnum=foo) PROP(QVariant variant) + PROP(Qt::DateFormat date = Qt::ISODate) + PROP(NS::NamespaceEnum nsEnum = NS::Alpha) + PROP(NS2::NamespaceEnum ns2Enum = NS2::NamespaceEnum::Alpha) SLOT(bool start()) SLOT(bool quit()) SIGNAL(advance()) - SIGNAL(enum2(MyEnumSignal myEnumSignal, MyEnumSignal sig2)) + SIGNAL(enum2(MyEnum myEnum1, MyEnum myEnum2)) + SLOT(MyEnum enumSlot(QPoint point, MyEnum myEnum)) + + SIGNAL(updateDate(Qt::DateFormat date1, Qt::DateFormat date2)) + SLOT(Qt::DateFormat dateSlot(Qt::DateFormat date)) CLASS subClass(SubClass) MODEL tracks(display) diff --git a/tools/repc/repcodegenerator.cpp b/tools/repc/repcodegenerator.cpp index 870033c..fd208c0 100644 --- a/tools/repc/repcodegenerator.cpp +++ b/tools/repc/repcodegenerator.cpp @@ -859,7 +859,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass out << " void " << property.name << "Changed(" << fullyQualifiedTypeName(astClass, className, typeForMode(property, mode)) << " " << property.name << ");" << endl; } - QVector<ASTFunction> signalsList = transformEnumParams(astClass, astClass.signalsList, className); + const QVector<ASTFunction> signalsList = transformEnumParams(astClass, astClass.signalsList, className); Q_FOREACH (const ASTFunction &signal, signalsList) out << " void " << signal.name << "(" << signal.paramsAsString() << ");" << endl; @@ -883,15 +883,16 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass out << "public Q_SLOTS:" << endl; Q_FOREACH (const ASTProperty &property, astClass.properties) { if (property.modifier == ASTProperty::ReadPush) { + const auto type = fullyQualifiedTypeName(astClass, className, property.type); if (mode != REPLICA) { - out << " virtual void push" << cap(property.name) << "(" << property.type << " " << property.name << ")" << endl; + out << " virtual void push" << cap(property.name) << "(" << type << " " << property.name << ")" << endl; out << " {" << endl; out << " set" << cap(property.name) << "(" << property.name << ");" << endl; out << " }" << endl; } else { - out << " void push" << cap(property.name) << "(" << property.type << " " << property.name << ")" << endl; + out << " void push" << cap(property.name) << "(" << type << " " << property.name << ")" << endl; out << " {" << endl; - out << " static int __repc_index = " << className << "::staticMetaObject.indexOfSlot(\"push" << cap(property.name) << "(" << property.type << ")\");" << endl; + out << " static int __repc_index = " << className << "::staticMetaObject.indexOfSlot(\"push" << cap(property.name) << "(" << type << ")\");" << endl; out << " QVariantList __repc_args;" << endl; out << " __repc_args << QVariant::fromValue(" << property.name << ");" << endl; out << " send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args);" << endl; @@ -899,9 +900,11 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass } } } - Q_FOREACH (const ASTFunction &slot, astClass.slotsList) { + const QVector<ASTFunction> slotsList = transformEnumParams(astClass, astClass.slotsList, className); + Q_FOREACH (const ASTFunction &slot, slotsList) { + const auto returnType = fullyQualifiedTypeName(astClass, className, slot.returnType); if (mode != REPLICA) { - out << " virtual " << slot.returnType << " " << slot.name << "(" << slot.paramsAsString() << ") = 0;" << endl; + out << " virtual " << returnType << " " << slot.name << "(" << slot.paramsAsString() << ") = 0;" << endl; } else { // TODO: Discuss whether it is a good idea to special-case for void here, const bool isVoid = slot.returnType == QStringLiteral("void"); @@ -909,7 +912,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass if (isVoid) out << " void " << slot.name << "(" << slot.paramsAsString() << ")" << endl; else - out << " QRemoteObjectPendingReply<" << slot.returnType << "> " << slot.name << "(" << slot.paramsAsString()<< ")" << endl; + out << " QRemoteObjectPendingReply<" << returnType << "> " << slot.name << "(" << slot.paramsAsString()<< ")" << endl; out << " {" << endl; out << " static int __repc_index = " << className << "::staticMetaObject.indexOfSlot(\"" << slot.name << "(" << slot.paramsAsString(ASTFunction::Normalized) << ")\");" << endl; out << " QVariantList __repc_args;" << endl; @@ -922,7 +925,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass if (isVoid) out << " send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args);" << endl; else - out << " return QRemoteObjectPendingReply<" << slot.returnType << ">(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args));" << endl; + out << " return QRemoteObjectPendingReply<" << returnType << ">(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args));" << endl; out << " }" << endl; } } @@ -1284,8 +1287,12 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl for (int i = 0; i < slotCount; ++i) { const ASTFunction &slot = astClass.slotsList.at(i); - out << QString::fromLatin1(" case %1: return QByteArrayLiteral(\"%2\");") - .arg(QString::number(i+pushCount), slot.returnType) << endl; + if (isClassEnum(astClass, slot.returnType)) + out << QString::fromLatin1(" case %1: return QByteArrayLiteral(\"$1\").replace(\"$1\", QtPrivate::qtro_enum_signature<ObjectType>(\"%2\"));") + .arg(QString::number(i+pushCount), slot.returnType) << endl; + else + out << QString::fromLatin1(" case %1: return QByteArrayLiteral(\"%2\");") + .arg(QString::number(i+pushCount), slot.returnType) << endl; } out << QStringLiteral(" }") << endl; } else |