/**************************************************************************** ** ** Copyright (C) 2017 Ford Motor Company ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtRemoteObjects module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qremoteobjectpacket_p.h" #include #include "qremoteobjectpendingcall.h" #include "qremoteobjectsource.h" #include "qremoteobjectsource_p.h" #include //#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(key.name()), seed) ^ qHash(static_cast(key.enumName()), seed) ^ qHash(static_cast(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); Q_ASSERT (propertyIndex >= 0); 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 (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::PointerToQObject)) { auto const childSource = source->m_children.value(internalIndex); auto valueAsPointerToQObject = qvariant_cast(value); if (childSource->m_object != valueAsPointerToQObject) childSource->resetObject(valueAsPointerToQObject); QRO_ qro(childSource); if (source->d->isDynamic && qro.type == ObjectType::CLASS && childSource->m_object && !source->d->sentTypes.contains(qro.typeName)) { QDataStream classDef(&qro.classDefinition, QIODevice::WriteOnly); serializeDefinition(classDef, childSource); source->d->sentTypes.insert(qro.typeName); } ds << QVariant::fromValue(qro); if (qro.isNull) return; const int propertyCount = childSource->m_api->propertyCount(); // Put the properties in a buffer, the receiver may not know how to // interpret the types until it registers new ones. QDataStream params(&qro.parameters, QIODevice::WriteOnly); params << propertyCount; for (int internalIndex = 0; internalIndex < propertyCount; ++internalIndex) serializeProperty(params, childSource, internalIndex); ds << qro.parameters; return; } if (source->d->isDynamic && property.userType() == QMetaType::QVariant && QMetaType::typeFlags(value.userType()).testFlag(QMetaType::IsGadget)) { const auto typeName = QString::fromLatin1(QMetaType::typeName(value.userType())); if (!source->d->sentTypes.contains(typeName)) { QRO_ qro(value); ds << QVariant::fromValue(qro); ds << qro.parameters; source->d->sentTypes.insert(typeName); return; } } ds << encodeVariant(value); } void serializeHandshakePacket(DataStreamPacket &ds) { ds.setId(Handshake); ds << QString(protocolVersion); ds.finishPacket(); } void serializeInitPacket(DataStreamPacket &ds, const QRemoteObjectRootSource *source) { ds.setId(InitPacket); ds << source->name(); serializeProperties(ds, source); ds.finishPacket(); } void serializeProperties(DataStreamPacket &ds, const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; //Now copy the property data const int numProperties = api->propertyCount(); ds << quint32(numProperties); //Number of properties for (int internalIndex = 0; internalIndex < numProperties; ++internalIndex) serializeProperty(ds, source, internalIndex); } bool deserializeQVariantList(QDataStream &s, QList &l) { // note: optimized version of: QDataStream operator>>(QDataStream& s, QList& l) quint32 c; s >> c; const int initialListSize = l.size(); if (static_cast(l.size()) < c) l.reserve(c); else if (static_cast(l.size()) > c) for (int i = c; i < initialListSize; ++i) l.removeLast(); for (int i = 0; i < l.size(); ++i) { if (s.atEnd()) return false; QVariant t; s >> t; l[i] = t; } for (quint32 i = l.size(); i < c; ++i) { if (s.atEnd()) return false; QVariant t; s >> t; l.append(t); } return true; } void deserializeInitPacket(QDataStream &in, QVariantList &values) { const bool success = deserializeQVariantList(in, values); Q_ASSERT(success); Q_UNUSED(success); } void serializeInitDynamicPacket(DataStreamPacket &ds, const QRemoteObjectRootSource *source) { ds.setId(InitDynamicPacket); ds << source->name(); serializeDefinition(ds, source); serializeProperties(ds, source); ds.finishPacket(); } static ObjectType getObjectType(const QString &typeName) { if (typeName == QLatin1String("QAbstractItemModelAdapter")) return ObjectType::MODEL; auto tid = QMetaType::type(typeName.toUtf8()); if (tid == QMetaType::UnknownType) return ObjectType::CLASS; QMetaType type(tid); auto mo = type.metaObject(); if (mo && mo->inherits(&QAbstractItemModel::staticMetaObject)) return ObjectType::MODEL; return ObjectType::CLASS; } // 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 &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 &gadgets, QSet &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 &gadgets, QSet &enums, const QRemoteObjectSourceBase *source) { const SourceApiMap *api = source->m_api; const int numSignals = api->signalCount(); const int numMethods = api->methodCount(); const int numProperties = api->propertyCount(); for (int si = 0; si < numSignals; ++si) { 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; recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } for (int mi = 0; mi < numMethods; ++mi) { 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; recurseMetaobject(mo, gadgets, enums); source->d->sentTypes.insert(QLatin1String(mo->className())); } } for (int pi = 0; pi < numProperties; ++pi) { const int index = api->sourcePropertyIndex(pi); Q_ASSERT(index >= 0); const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object; const auto metaProperty = target->metaObject()->property(index); 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, enums, childSource); } } if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget)) continue; const auto mo = QMetaType::metaObjectForType(type); if (source->d->sentTypes.contains(QLatin1String(mo->className()))) continue; 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 &gadgets, const QSet &enums, const QRemoteObjectSourceBase *source=nullptr) { // Determine how to handle the enums found QSet qtEnums; QSet 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; 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(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << "Serializing definition for" << source->m_api->typeName(); #endif //Now copy the property data const int numEnums = api->enumCount(); const auto metaObject = source->m_object->metaObject(); ds << quint32(numEnums); //Number of Enums #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Found" << numEnums << "enumeration types"; #endif for (int i = 0; i < numEnums; ++i) { auto enumerator = metaObject->enumerator(api->sourceEnumIndex(i)); Q_ASSERT(enumerator.isValid()); serializeEnum(ds, enumerator); } if (source->d->isDynamic) { QSet gadgets; QSet enums; recurseForGadgets(gadgets, enums, source); serializeGadgets(ds, gadgets, enums, source); } else ds << quint32(0) << quint32(0); // qtEnums, numGadgets const int numSignals = api->signalCount(); ds << quint32(numSignals); //Number of signals for (int i = 0; i < numSignals; ++i) { const int index = api->sourceSignalIndex(i); Q_ASSERT(index >= 0); auto signature = api->signalSignature(i); replace(signature); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Signal" << i << "(signature =" << signature << "parameter names =" << api->signalParameterNames(i) << ")"; #endif ds << signature; ds << api->signalParameterNames(i); } const int numMethods = api->methodCount(); ds << quint32(numMethods); //Number of methods 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 =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")"; #endif ds << signature; ds << typeName; ds << api->methodParameterNames(i); } const int numProperties = api->propertyCount(); ds << quint32(numProperties); //Number of properties for (int i = 0; i < numProperties; ++i) { const int index = api->sourcePropertyIndex(i); Q_ASSERT(index >= 0); const auto target = api->isAdapterProperty(i) ? source->m_adapter : source->m_object; const auto metaProperty = target->metaObject()->property(index); ds << metaProperty.name(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Property" << i << "name =" << metaProperty.name(); #endif if (QMetaType::typeFlags(metaProperty.userType()).testFlag(QMetaType::PointerToQObject)) { auto objectType = getObjectType(QLatin1String(metaProperty.typeName())); ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*"); #endif } else { ds << metaProperty.typeName(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Type:" << metaProperty.typeName(); #endif } if (metaProperty.notifySignalIndex() == -1) { ds << QByteArray(); #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Notification signal: None"; #endif } else { auto signature = metaProperty.notifySignal().methodSignature(); replace(signature); ds << signature; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Notification signal:" << signature; #endif } } } void serializeAddObjectPacket(DataStreamPacket &ds, const QString &name, bool isDynamic) { ds.setId(AddObject); ds << name; ds << isDynamic; ds.finishPacket(); } void deserializeAddObjectPacket(QDataStream &ds, bool &isDynamic) { ds >> isDynamic; } void serializeRemoveObjectPacket(DataStreamPacket &ds, const QString &name) { ds.setId(RemoveObject); ds << name; ds.finishPacket(); } //There is no deserializeRemoveObjectPacket - no parameters other than id and name void serializeInvokePacket(DataStreamPacket &ds, const QString &name, int call, int index, const QVariantList &args, int serialId, int propertyIndex) { ds.setId(InvokePacket); ds << name; ds << call; ds << index; ds << (quint32)args.size(); foreach (const auto &arg, args) ds << encodeVariant(arg); ds << serialId; ds << propertyIndex; ds.finishPacket(); } void deserializeInvokePacket(QDataStream& in, int &call, int &index, QVariantList &args, int &serialId, int &propertyIndex) { in >> call; in >> index; const bool success = deserializeQVariantList(in, args); Q_ASSERT(success); Q_UNUSED(success); in >> serialId; in >> propertyIndex; } void serializeInvokeReplyPacket(DataStreamPacket &ds, const QString &name, int ackedSerialId, const QVariant &value) { ds.setId(InvokeReplyPacket); ds << name; ds << ackedSerialId; ds << value; ds.finishPacket(); } void deserializeInvokeReplyPacket(QDataStream& in, int &ackedSerialId, QVariant &value){ in >> ackedSerialId; in >> value; } void serializePropertyChangePacket(QRemoteObjectSourceBase *source, int signalIndex) { int internalIndex = source->m_api->propertyRawIndexFromSignal(signalIndex); auto &ds = source->d->m_packet; ds.setId(PropertyChangePacket); ds << source->name(); ds << internalIndex; serializeProperty(ds, source, internalIndex); ds.finishPacket(); } void deserializePropertyChangePacket(QDataStream& in, int &index, QVariant &value) { in >> index; in >> value; } void serializeObjectListPacket(DataStreamPacket &ds, const ObjectInfoList &objects) { ds.setId(ObjectList); ds << objects; ds.finishPacket(); } void deserializeObjectListPacket(QDataStream &in, ObjectInfoList &objects) { in >> objects; } void serializePingPacket(DataStreamPacket &ds, const QString &name) { ds.setId(Ping); ds << name; ds.finishPacket(); } void serializePongPacket(DataStreamPacket &ds, const QString &name) { ds.setId(Pong); ds << name; ds.finishPacket(); } QRO_::QRO_(QRemoteObjectSourceBase *source) : name(source->name()) , typeName(source->m_api->typeName()) , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName)) , isNull(source->m_object == nullptr) , classDefinition() , parameters() {} QRO_::QRO_(const QVariant &value) : type(ObjectType::GADGET) , isNull(false) { auto meta = QMetaType::metaObjectForType(value.userType()); QDataStream out(&classDefinition, QIODevice::WriteOnly); const int numProperties = meta->propertyCount(); 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 qDebug("Serializing POD definition to QRO_ (name = %s)", typeName.constData()); #endif for (int i = 0; i < numProperties; ++i) { const auto property = meta->property(i); #ifdef QTRO_VERBOSE_PROTOCOL qDebug(" Data member %d (name = %s, type = %s):", i, property.name(), property.typeName()); #endif out << QByteArray::fromRawData(property.name(), qstrlen(property.name())); out << QByteArray::fromRawData(property.typeName(), qstrlen(property.typeName())); } QDataStream ds(¶meters, QIODevice::WriteOnly); ds << value; #ifdef QTRO_VERBOSE_PROTOCOL qDebug() << " Value:" << value; #endif } QDataStream &operator<<(QDataStream &stream, const QRO_ &info) { stream << info.name << info.typeName << (quint8)(info.type) << info.classDefinition << info.isNull; qCDebug(QT_REMOTEOBJECT) << "Serializing " << info; // info.parameters will be filled in by serializeProperty return stream; } QDataStream &operator>>(QDataStream &stream, QRO_ &info) { quint8 tmpType; stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull; info.type = static_cast(tmpType); qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info; if (!info.isNull) stream >> info.parameters; return stream; } } // namespace QRemoteObjectPackets QT_END_NAMESPACE