1// Copyright (C) 2017 Ford Motor Company
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/qabstractitemmodel.h>
5#include <QtCore/qbytearrayview.h>
6
7#include "qremoteobjectcontainers_p.h"
8#include "qremoteobjectpendingcall.h"
9#include "qremoteobjectsource.h"
10#include "qremoteobjectsource_p.h"
11#include "qremoteobjectpacket_p.h"
12#include "qconnectionfactories.h"
13#include "qconnectionfactories_p.h"
14#include <cstring>
15
16//#define QTRO_VERBOSE_PROTOCOL
17QT_BEGIN_NAMESPACE
18
19
20// Add methods so we can use QMetaEnum in a set
21// Note for both functions we are skipping string comparisons/hashes. Since the
22// metaObjects are the same, we can just use the address of the string.
23inline bool operator==(const QMetaEnum e1, const QMetaEnum e2)
24{
25 return e1.enclosingMetaObject() == e2.enclosingMetaObject()
26 && e1.name() == e2.name()
27 && e1.enumName() == e2.enumName()
28 && e1.scope() == e2.scope();
29}
30
31inline size_t qHash(const QMetaEnum &key, size_t seed=0) Q_DECL_NOTHROW
32{
33 return qHash(key: key.enclosingMetaObject(), seed) ^ qHash(key: static_cast<const void *>(key.name()), seed)
34 ^ qHash(key: static_cast<const void *>(key.enumName()), seed) ^ qHash(key: static_cast<const void *>(key.scope()), seed);
35}
36
37static bool isSequentialGadgetType(QMetaType metaType)
38{
39 if (QMetaType::canConvert(fromType: metaType, toType: QMetaType::fromType<QSequentialIterable>())) {
40 static QHash<int, bool> lookup;
41 if (!lookup.contains(key: metaType.id())) {
42 auto stubVariant = QVariant(metaType, nullptr);
43 auto asIterable = stubVariant.value<QSequentialIterable>();
44 auto valueMetaType = asIterable.metaContainer().valueMetaType();
45 lookup[metaType.id()] = valueMetaType.flags().testFlag(flag: QMetaType::IsGadget);
46 }
47 return lookup[metaType.id()];
48 }
49 return false;
50}
51
52static bool isAssociativeGadgetType(QMetaType metaType)
53{
54 if (QMetaType::canConvert(fromType: metaType, toType: QMetaType::fromType<QAssociativeIterable>())) {
55 static QHash<int, bool> lookup;
56 if (!lookup.contains(key: metaType.id())) {
57 auto stubVariant = QVariant(metaType, nullptr);
58 auto asIterable = stubVariant.value<QAssociativeIterable>();
59 auto valueMetaType = asIterable.metaContainer().mappedMetaType();
60 lookup[metaType.id()] = valueMetaType.flags().testFlag(flag: QMetaType::IsGadget);
61 }
62 return lookup[metaType.id()];
63 }
64 return false;
65}
66
67using namespace QtRemoteObjects;
68
69namespace QRemoteObjectPackets {
70
71QMetaType transferTypeForEnum(QMetaType enumType)
72{
73 const auto size = enumType.sizeOf();
74 switch (size) {
75 case 1: return QMetaType::fromType<qint8>();
76 case 2: return QMetaType::fromType<qint16>();
77 case 4: return QMetaType::fromType<qint32>();
78 // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int)
79// case 8: args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break;
80 default:
81 qCWarning(QT_REMOTEOBJECT_IO) << "Invalid enum detected (Dynamic Replica)" << enumType.name() << "with size" << size;
82 return QMetaType::fromType<qint32>();
83 }
84}
85
86// QDataStream sends QVariants of custom types by sending their typename, allowing decode
87// on the receiving side. For QtRO and enums, this won't work, as the enums have different
88// scopes. E.g., the examples have ParentClassSource::MyEnum and ParentClassReplica::MyEnum.
89// Dynamic types will be created as ParentClass::MyEnum. So instead, we change the variants
90// to integers (encodeVariant) when sending them. On the receive side, the we know the
91// types of properties and the signatures for methods, so we can use that information to
92// decode the integer variant into an enum variant (via decodeVariant).
93QVariant encodeVariant(const QVariant &value)
94{
95 const auto metaType = value.metaType();
96 if (metaType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
97 auto converted = QVariant(value);
98 auto transferType = transferTypeForEnum(enumType: metaType);
99 converted.convert(type: transferType);
100#ifdef QTRO_VERBOSE_PROTOCOL
101 qDebug() << "Converting from enum to integer type" << transferType.sizeOf() << converted << value;
102#endif
103 return converted;
104 }
105 if (isSequentialGadgetType(metaType)) { // Doesn't include QtROSequentialContainer
106 // TODO Way to create the QVariant without copying the QSQ_?
107 QSQ_ sequence(value);
108#ifdef QTRO_VERBOSE_PROTOCOL
109 qDebug() << "Encoding sequential container" << metaType.name() << "to QSQ_ to transmit";
110#endif
111 return QVariant::fromValue<QSQ_>(value: sequence);
112 }
113 if (metaType == QMetaType::fromType<QtROSequentialContainer>()) {
114 QSQ_ sequence(value);
115#ifdef QTRO_VERBOSE_PROTOCOL
116 qDebug() << "Encoding QtROSequentialContainer container to QSQ_ to transmit";
117#endif
118 return QVariant::fromValue<QSQ_>(value: sequence);
119 }
120 if (isAssociativeGadgetType(metaType)) { // Doesn't include QtROAssociativeContainer
121 QAS_ map(value);
122#ifdef QTRO_VERBOSE_PROTOCOL
123 qDebug() << "Encoding associative container" << metaType.name() << "to QAS_ to transmit";
124#endif
125 return QVariant::fromValue<QAS_>(value: map);
126 }
127 if (metaType == QMetaType::fromType<QtROAssociativeContainer>()) {
128 QAS_ map(value);
129#ifdef QTRO_VERBOSE_PROTOCOL
130 qDebug() << "Encoding QtROAssociativeContainer container to QAS_ to transmit";
131#endif
132 return QVariant::fromValue<QAS_>(value: map);
133 }
134 return value;
135}
136
137QVariant decodeVariant(QVariant &&value, QMetaType metaType)
138{
139 if (metaType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
140#ifdef QTRO_VERBOSE_PROTOCOL
141 QVariant encoded(value);
142#endif
143 value.convert(type: metaType);
144#ifdef QTRO_VERBOSE_PROTOCOL
145 qDebug() << "Converting to enum from integer type" << value << encoded;
146#endif
147 } else if (value.metaType() == QMetaType::fromType<QRemoteObjectPackets::QSQ_>()) {
148 const auto *qsq_ = static_cast<const QRemoteObjectPackets::QSQ_ *>(value.constData());
149 QDataStream in(qsq_->values);
150 auto containerType = QMetaType::fromName(name: qsq_->typeName.constData());
151 bool isRegistered = containerType.isRegistered();
152 if (isRegistered) {
153 QVariant seq{containerType, nullptr};
154 if (!seq.canView<QSequentialIterable>()) {
155 qWarning() << "Unsupported container" << qsq_->typeName.constData()
156 << "(not viewable)";
157 return QVariant();
158 }
159 QSequentialIterable seqIter = seq.view<QSequentialIterable>();
160 if (!seqIter.metaContainer().canAddValue()) {
161 qWarning() << "Unsupported container" << qsq_->typeName.constData()
162 << "(Unable to add values)";
163 return QVariant();
164 }
165 QByteArray valueTypeName;
166 quint32 count;
167 in >> valueTypeName;
168 in >> count;
169 QMetaType valueType = QMetaType::fromName(name: valueTypeName.constData());
170 QVariant tmp{valueType, nullptr};
171 for (quint32 i = 0; i < count; i++) {
172 if (!valueType.load(stream&: in, data: tmp.data())) {
173 if (seqIter.metaContainer().canRemoveValue() || i == 0) {
174 for (quint32 ii = 0; ii < i; ii++)
175 seqIter.removeValue();
176 qWarning(msg: "QSQ_: unable to load type '%s', returning an empty list.", valueTypeName.constData());
177 } else {
178 qWarning(msg: "QSQ_: unable to load type '%s', returning a partial list.", valueTypeName.constData());
179 }
180 break;
181 }
182 seqIter.addValue(value: tmp);
183 }
184 value = seq;
185#ifdef QTRO_VERBOSE_PROTOCOL
186 qDebug() << "Decoding QSQ_ to sequential container" << containerType.name()
187 << valueTypeName;
188#endif
189 } else {
190 QtROSequentialContainer container{};
191 in >> container;
192 container.m_typeName = qsq_->typeName;
193 value = QVariant(QMetaType::fromType<QtROSequentialContainer>(), &container);
194#ifdef QTRO_VERBOSE_PROTOCOL
195 qDebug() << "Decoding QSQ_ to QtROSequentialContainer of"
196 << container.m_valueTypeName;
197#endif
198 }
199 } else if (value.metaType() == QMetaType::fromType<QRemoteObjectPackets::QAS_>()) {
200 const auto *qas_ = static_cast<const QRemoteObjectPackets::QAS_ *>(value.constData());
201 QDataStream in(qas_->values);
202 auto containerType = QMetaType::fromName(name: qas_->typeName.constData());
203 bool isRegistered = containerType.isRegistered();
204 if (isRegistered) {
205 QVariant map{containerType, nullptr};
206 if (!map.canView<QAssociativeIterable>()) {
207 qWarning() << "Unsupported container" << qas_->typeName.constData()
208 << "(not viewable)";
209 return QVariant();
210 }
211 QAssociativeIterable mapIter = map.view<QAssociativeIterable>();
212 if (!mapIter.metaContainer().canSetMappedAtKey()) {
213 qWarning() << "Unsupported container" << qas_->typeName.constData()
214 << "(Unable to insert values)";
215 return QVariant();
216 }
217 QByteArray keyTypeName, valueTypeName;
218 quint32 count;
219 in >> keyTypeName;
220 QMetaType keyType = QMetaType::fromName(name: keyTypeName.constData());
221 if (!keyType.isValid()) {
222 // This happens for class enums, where the passed keyType is <ClassName>::<enum>
223 // For a compiled replica, the keyType is <ClassName>Replica::<enum>
224 // Since the full typename is registered, we can pull the keyType from there
225 keyType = mapIter.metaContainer().keyMetaType();
226 }
227 QMetaType transferType = keyType;
228 if (keyType.flags().testFlag(flag: QMetaType::IsEnumeration))
229 transferType = transferTypeForEnum(enumType: keyType);
230 QVariant key{transferType, nullptr};
231 in >> valueTypeName;
232 QMetaType valueType = QMetaType::fromName(name: valueTypeName.constData());
233 QVariant val{valueType, nullptr};
234 in >> count;
235 for (quint32 i = 0; i < count; i++) {
236 if (!transferType.load(stream&: in, data: key.data())) {
237 map = QVariant{containerType, nullptr};
238 qWarning(msg: "QAS_: unable to load key of type '%s', returning an empty map.",
239 keyTypeName.constData());
240 break;
241 }
242 if (!valueType.load(stream&: in, data: val.data())) {
243 map = QVariant{containerType, nullptr};
244 qWarning(msg: "QAS_: unable to load value of type '%s', returning an empty map.",
245 valueTypeName.constData());
246 break;
247 }
248 if (transferType != keyType) {
249 QVariant enumKey(key);
250 enumKey.convert(type: keyType);
251 mapIter.setValue(key: enumKey, mapped: val);
252 } else {
253 mapIter.setValue(key, mapped: val);
254 }
255 }
256 value = map;
257#ifdef QTRO_VERBOSE_PROTOCOL
258 qDebug() << "Decoding QAS_ to associative container" << containerType.name()
259 << valueTypeName << keyTypeName << count << mapIter.size() << map;
260#endif
261 } else {
262 QtROAssociativeContainer container{};
263 in >> container;
264 container.m_typeName = qas_->typeName;
265 value = QVariant(QMetaType::fromType<QtROAssociativeContainer>(), &container);
266#ifdef QTRO_VERBOSE_PROTOCOL
267 qDebug() << "Decoding QAS_ to QtROAssociativeContainer of"
268 << container.m_valueTypeName;
269#endif
270 }
271 }
272 return std::move(value);
273}
274
275void QDataStreamCodec::serializeProperty(const QRemoteObjectSourceBase *source, int internalIndex)
276{
277 serializeProperty(ds&: m_packet, source, internalIndex);
278}
279
280void QDataStreamCodec::serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, int internalIndex)
281{
282 const int propertyIndex = source->m_api->sourcePropertyIndex(index: internalIndex);
283 Q_ASSERT (propertyIndex >= 0);
284 const auto target = source->m_api->isAdapterProperty(internalIndex) ? source->m_adapter : source->m_object;
285 const auto property = target->metaObject()->property(index: propertyIndex);
286 const QVariant value = property.read(obj: target);
287 if (property.metaType().flags().testFlag(flag: QMetaType::PointerToQObject)) {
288 auto const childSource = source->m_children.value(key: internalIndex);
289 auto valueAsPointerToQObject = qvariant_cast<QObject *>(v: value);
290 if (childSource->m_object != valueAsPointerToQObject)
291 childSource->resetObject(newObject: valueAsPointerToQObject);
292 QRO_ qro(childSource);
293 if (source->d->isDynamic && qro.type == ObjectType::CLASS && childSource->m_object && !source->d->sentTypes.contains(value: qro.typeName)) {
294 QDataStream classDef(&qro.classDefinition, QIODevice::WriteOnly);
295 serializeDefinition(classDef, childSource);
296 source->d->sentTypes.insert(value: qro.typeName);
297 }
298 ds << QVariant::fromValue<QRO_>(value: qro);
299 if (qro.isNull)
300 return;
301 const int propertyCount = childSource->m_api->propertyCount();
302 // Put the properties in a buffer, the receiver may not know how to
303 // interpret the types until it registers new ones.
304 QDataStream params(&qro.parameters, QIODevice::WriteOnly);
305 params << propertyCount;
306 for (int internalIndex = 0; internalIndex < propertyCount; ++internalIndex)
307 serializeProperty(ds&: params, source: childSource, internalIndex);
308 ds << qro.parameters;
309 return;
310 }
311 if (source->d->isDynamic && property.userType() == QMetaType::QVariant
312 && value.metaType().flags().testFlag(flag: QMetaType::IsGadget)) {
313 const auto typeName = QString::fromLatin1(ba: value.metaType().name());
314 if (!source->d->sentTypes.contains(value: typeName)) {
315 QRO_ qro(value);
316 ds << QVariant::fromValue<QRO_>(value: qro);
317 ds << qro.parameters;
318 source->d->sentTypes.insert(value: typeName);
319 return;
320 }
321 }
322 ds << encodeVariant(value);
323}
324
325void QDataStreamCodec::serializeHandshakePacket()
326{
327 m_packet.setId(Handshake);
328 m_packet << QString(protocolVersion);
329 m_packet.finishPacket();
330}
331
332void QDataStreamCodec::serializeInitPacket(const QRemoteObjectRootSource *source)
333{
334 m_packet.setId(InitPacket);
335 m_packet << source->name();
336 serializeProperties(source);
337 m_packet.finishPacket();
338}
339
340void QDataStreamCodec::serializeProperties(const QRemoteObjectSourceBase *source)
341{
342 const SourceApiMap *api = source->m_api;
343
344 //Now copy the property data
345 const int numProperties = api->propertyCount();
346 m_packet << quint32(numProperties); //Number of properties
347
348 for (int internalIndex = 0; internalIndex < numProperties; ++internalIndex)
349 serializeProperty(source, internalIndex);
350}
351
352bool deserializeQVariantList(QDataStream &s, QVariantList &l)
353{
354 // note: optimized version of: QDataStream operator>>(QDataStream& s, QList<T>& l)
355 quint32 c;
356 s >> c;
357
358 const qsizetype count = static_cast<qsizetype>(c);
359 const qsizetype listSize = l.size();
360 if (listSize < count)
361 l.reserve(asize: count);
362 else if (listSize > count)
363 l.resize(size: count);
364
365 for (int i = 0; i < l.size(); ++i)
366 {
367 if (s.atEnd())
368 return false;
369 s >> l[i];
370 }
371 for (auto i = l.size(); i < count; ++i)
372 {
373 if (s.atEnd())
374 return false;
375 s >> l.emplace_back();
376 }
377 return true;
378}
379
380void QDataStreamCodec::deserializeInitPacket(QDataStream &in, QVariantList &values)
381{
382 const bool success = deserializeQVariantList(s&: in, l&: values);
383 Q_ASSERT(success);
384 Q_UNUSED(success)
385}
386
387void QDataStreamCodec::serializeInitDynamicPacket(const QRemoteObjectRootSource *source)
388{
389 m_packet.setId(InitDynamicPacket);
390 m_packet << source->name();
391 serializeDefinition(m_packet, source);
392 serializeProperties(source);
393 m_packet.finishPacket();
394}
395
396static ObjectType getObjectType(const QString &typeName)
397{
398 if (typeName == QLatin1String("QAbstractItemModelAdapter"))
399 return ObjectType::MODEL;
400 auto tid = QMetaType::fromName(name: typeName.toUtf8()).id();
401 if (tid == QMetaType::UnknownType)
402 return ObjectType::CLASS;
403 QMetaType type(tid);
404 auto mo = type.metaObject();
405 if (mo && mo->inherits(metaObject: &QAbstractItemModel::staticMetaObject))
406 return ObjectType::MODEL;
407 return ObjectType::CLASS;
408}
409
410static QByteArrayView resolveEnumName(QMetaType t, bool &isFlag)
411{
412 // Takes types like `MyPOD::Position` or `QFlags<MyPOD::Position>` and returns 'Position`
413 QByteArrayView enumName(t.name());
414 isFlag = enumName.startsWith(other: "QFlags<");
415 auto lastColon = enumName.lastIndexOf(ch: ':');
416 if (lastColon >= 0)
417 enumName = QByteArrayView(t.name() + lastColon + 1);
418 if (isFlag)
419 enumName.chop(n: 1);
420 return enumName;
421}
422
423static QMetaEnum metaEnumFromType(QMetaType t)
424{
425 if (t.flags().testFlag(flag: QMetaType::IsEnumeration)) {
426 if (const QMetaObject *m = t.metaObject()) {
427 bool isFlag;
428 auto enumName = resolveEnumName(t, isFlag);
429 if (isFlag) {
430 for (int i = m->enumeratorOffset(); i < m->enumeratorCount(); i++) {
431 auto testType = m->enumerator(index: i);
432 if (testType.isFlag() &&
433 enumName.compare(a: QByteArrayView(testType.enumName())) == 0)
434 return testType;
435 }
436 }
437 return m->enumerator(index: m->indexOfEnumerator(name: enumName.data()));
438 }
439 }
440 return QMetaEnum();
441}
442
443static bool checkEnum(QMetaType metaType, QSet<QMetaEnum> &enums)
444{
445 if (metaType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
446 QMetaEnum meta = metaEnumFromType(t: metaType);
447 enums.insert(value: meta);
448 return true;
449 }
450 return false;
451}
452
453static void recurseMetaobject(const QMetaObject *mo, QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums)
454{
455 if (!mo || gadgets.contains(value: mo))
456 return;
457 gadgets.insert(value: mo);
458 const int numProperties = mo->propertyCount();
459 for (int i = 0; i < numProperties; ++i) {
460 const auto property = mo->property(index: i);
461 if (checkEnum(metaType: property.metaType(), enums))
462 continue;
463 if (property.metaType().flags().testFlag(flag: QMetaType::IsGadget))
464 recurseMetaobject(mo: property.metaType().metaObject(), gadgets, enums);
465 }
466}
467
468// A Source may only use a subset of the metaobjects properties/signals/slots, so we only search
469// the ones in the API. For nested pointer types, we will have another api to limit the search.
470// For nested PODs/enums, we search the entire qobject (using the recurseMetaobject call()).
471void recurseForGadgets(QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source)
472{
473 const SourceApiMap *api = source->m_api;
474
475 const int numSignals = api->signalCount();
476 const int numMethods = api->methodCount();
477 const int numProperties = api->propertyCount();
478
479 for (int si = 0; si < numSignals; ++si) {
480 const int params = api->signalParameterCount(index: si);
481 for (int pi = 0; pi < params; ++pi) {
482 const int type = api->signalParameterType(sigIndex: si, paramIndex: pi);
483 const auto metaType = QMetaType(type);
484 if (checkEnum(metaType, enums))
485 continue;
486 if (!metaType.flags().testFlag(flag: QMetaType::IsGadget))
487 continue;
488 const auto mo = metaType.metaObject();
489 if (source->d->sentTypes.contains(value: QLatin1String(mo->className())))
490 continue;
491 recurseMetaobject(mo, gadgets, enums);
492 source->d->sentTypes.insert(value: QLatin1String(mo->className()));
493 }
494 }
495
496 for (int mi = 0; mi < numMethods; ++mi) {
497 const int params = api->methodParameterCount(index: mi);
498 for (int pi = 0; pi < params; ++pi) {
499 const int type = api->methodParameterType(methodIndex: mi, paramIndex: pi);
500 const auto metaType = QMetaType(type);
501 if (checkEnum(metaType, enums))
502 continue;
503 if (!metaType.flags().testFlag(flag: QMetaType::IsGadget))
504 continue;
505 const auto mo = metaType.metaObject();
506 if (source->d->sentTypes.contains(value: QLatin1String(mo->className())))
507 continue;
508 recurseMetaobject(mo, gadgets, enums);
509 source->d->sentTypes.insert(value: QLatin1String(mo->className()));
510 }
511 }
512 for (int pi = 0; pi < numProperties; ++pi) {
513 const int index = api->sourcePropertyIndex(index: pi);
514 Q_ASSERT(index >= 0);
515 const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object;
516 const auto metaProperty = target->metaObject()->property(index);
517 const auto metaType = metaProperty.metaType();
518 if (checkEnum(metaType, enums))
519 continue;
520 if (metaType.flags().testFlag(flag: QMetaType::PointerToQObject)) {
521 auto const objectType = getObjectType(typeName: QString::fromLatin1(ba: metaProperty.typeName()));
522 if (objectType == ObjectType::CLASS) {
523 auto const childSource = source->m_children.value(key: pi);
524 if (childSource->m_object)
525 recurseForGadgets(gadgets, enums, source: childSource);
526 }
527 }
528 if (!metaType.flags().testFlag(flag: QMetaType::IsGadget))
529 continue;
530 const auto mo = metaType.metaObject();
531 if (source->d->sentTypes.contains(value: QLatin1String(mo->className())))
532 continue;
533 recurseMetaobject(mo, gadgets, enums);
534 source->d->sentTypes.insert(value: QLatin1String(mo->className()));
535 }
536}
537
538static bool checkForEnumsInSource(const QMetaObject *meta, const QRemoteObjectSourceBase *source)
539{
540 if (source->m_object->inherits(classname: meta->className()))
541 return true;
542 for (const auto &child : source->m_children) {
543 if (child->m_object && checkForEnumsInSource(meta, source: child))
544 return true;
545 }
546 return false;
547}
548
549static void serializeEnum(QDataStream &ds, const QMetaEnum &enumerator)
550{
551 ds << QByteArray::fromRawData(data: enumerator.name(), size: qsizetype(qstrlen(str: enumerator.name())));
552 ds << enumerator.isFlag();
553 ds << enumerator.isScoped();
554 const auto typeName = QByteArray(enumerator.scope()).append(s: "::").append(s: enumerator.name());
555 const quint32 size = quint32(QMetaType::fromName(name: typeName.constData()).sizeOf());
556 ds << size;
557#ifdef QTRO_VERBOSE_PROTOCOL
558 qDebug(" Enum (name = %s, size = %d, isFlag = %s, isScoped = %s):", enumerator.name(), size, enumerator.isFlag() ? "true" : "false", enumerator.isScoped() ? "true" : "false");
559#endif
560 const int keyCount = enumerator.keyCount();
561 ds << keyCount;
562 for (int k = 0; k < keyCount; ++k) {
563 ds << QByteArray::fromRawData(data: enumerator.key(index: k), size: qsizetype(qstrlen(str: enumerator.key(index: k))));
564 ds << enumerator.value(index: k);
565#ifdef QTRO_VERBOSE_PROTOCOL
566 qDebug(" Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k));
567#endif
568 }
569}
570
571static void serializeGadgets(QDataStream &ds, const QSet<const QMetaObject *> &gadgets, const QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source=nullptr)
572{
573 // Determine how to handle the enums found
574 QSet<QMetaEnum> qtEnums;
575 QSet<const QMetaObject *> dynamicEnumMetaObjects;
576 for (const auto &metaEnum : enums) {
577 auto const metaObject = metaEnum.enclosingMetaObject();
578 if (gadgets.contains(value: metaObject)) // Part of a gadget will we serialize
579 continue;
580 // This checks if the enum is defined in our object heirarchy, in which case it will
581 // already have been serialized.
582 if (source && checkForEnumsInSource(meta: metaObject, source: source->d->root))
583 continue;
584 // qtEnums are enumerations already known by Qt, so we only need register them.
585 // We don't need to send all of the key/value data.
586 if (metaObject == &Qt::staticMetaObject) // Are the other Qt metaclasses for enums?
587 qtEnums.insert(value: metaEnum);
588 else
589 dynamicEnumMetaObjects.insert(value: metaEnum.enclosingMetaObject());
590 }
591 ds << quint32(qtEnums.size());
592 for (const auto &metaEnum : qtEnums) {
593 QByteArray enumName(metaEnum.scope());
594 enumName.append(s: "::", len: 2).append(s: metaEnum.name());
595 ds << enumName;
596 }
597 const auto allMetaObjects = gadgets + dynamicEnumMetaObjects;
598 ds << quint32(allMetaObjects.size());
599#ifdef QTRO_VERBOSE_PROTOCOL
600 qDebug() << " Found" << gadgets.size() << "gadget/pod and" << (allMetaObjects.size() - gadgets.size()) << "enum types";
601 int i = 0;
602#endif
603 // There isn't an easy way to update a metaobject incrementally, so we
604 // send all of the metaobject's enums, but no properties, when an external
605 // enum is requested.
606 for (auto const meta : allMetaObjects) {
607 ds << QByteArray::fromRawData(data: meta->className(), size: qsizetype(qstrlen(str: meta->className())));
608 int propertyCount = gadgets.contains(value: meta) ? meta->propertyCount() : 0;
609 ds << quint32(propertyCount);
610#ifdef QTRO_VERBOSE_PROTOCOL
611 qDebug(" Gadget %d (name = %s, # properties = %d, # enums = %d):", i++, meta->className(), propertyCount, meta->enumeratorCount() - meta->enumeratorOffset());
612#endif
613 for (int j = 0; j < propertyCount; j++) {
614 auto prop = meta->property(index: j);
615#ifdef QTRO_VERBOSE_PROTOCOL
616 qDebug(" Data member %d (name = %s, type = %s):", j, prop.name(), prop.typeName());
617#endif
618 ds << QByteArray::fromRawData(data: prop.name(), size: qsizetype(qstrlen(str: prop.name())));
619 ds << QByteArray::fromRawData(data: prop.typeName(), size: qsizetype(qstrlen(str: prop.typeName())));
620 }
621 int enumCount = meta->enumeratorCount() - meta->enumeratorOffset();
622 ds << quint32(enumCount);
623 for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) {
624 auto const enumMeta = meta->enumerator(index: j);
625 serializeEnum(ds, enumerator: enumMeta);
626 }
627 }
628}
629
630void QDataStreamCodec::serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source)
631{
632 const SourceApiMap *api = source->m_api;
633 const QByteArray desiredClassName(api->typeName().toLatin1());
634 const QByteArray originalClassName = api->className();
635 // The dynamic class will be called typeName on the receiving side of this definition
636 // However, there are types like enums that have the QObject's class name. Replace()
637 // will convert a parameter such as "ParentClassSource::MyEnum" to "ParentClass::MyEnum"
638 // so the type can be properly resolved and registered.
639 auto replace = [&originalClassName, &desiredClassName](QByteArray &name) {
640 name.replace(before: originalClassName, after: desiredClassName);
641 };
642
643 ds << source->m_api->typeName();
644#ifdef QTRO_VERBOSE_PROTOCOL
645 qDebug() << "Serializing definition for" << source->m_api->typeName();
646#endif
647
648 //Now copy the property data
649 const int numEnums = api->enumCount();
650 const auto metaObject = source->m_object->metaObject();
651 ds << quint32(numEnums); //Number of Enums
652#ifdef QTRO_VERBOSE_PROTOCOL
653 qDebug() << " Found" << numEnums << "enumeration types";
654#endif
655 for (int i = 0; i < numEnums; ++i) {
656 auto enumerator = metaObject->enumerator(index: api->sourceEnumIndex(index: i));
657 Q_ASSERT(enumerator.isValid());
658 serializeEnum(ds, enumerator);
659 }
660
661 QSet<const QMetaObject *> gadgets;
662 QSet<QMetaEnum> enums;
663 recurseForGadgets(gadgets, enums, source);
664 serializeGadgets(ds, gadgets, enums, source);
665
666 const int numSignals = api->signalCount();
667 ds << quint32(numSignals); //Number of signals
668 for (int i = 0; i < numSignals; ++i) {
669 const int index = api->sourceSignalIndex(index: i);
670 Q_ASSERT(index >= 0);
671 auto signature = api->signalSignature(index: i);
672 replace(signature);
673 const int count = api->signalParameterCount(index: i);
674 for (int pi = 0; pi < count; ++pi) {
675 const auto metaType = QMetaType(api->signalParameterType(sigIndex: i, paramIndex: pi));
676 if (isSequentialGadgetType(metaType))
677 signature.replace(before: metaType.name(), after: "QtROSequentialContainer");
678 else if (isAssociativeGadgetType(metaType))
679 signature.replace(before: metaType.name(), after: "QtROAssociativeContainer");
680 }
681#ifdef QTRO_VERBOSE_PROTOCOL
682 qDebug() << " Signal" << i << "(signature =" << signature << "parameter names =" << api->signalParameterNames(i) << ")";
683#endif
684 ds << signature;
685 ds << api->signalParameterNames(index: i);
686 }
687
688 const int numMethods = api->methodCount();
689 ds << quint32(numMethods); //Number of methods
690 for (int i = 0; i < numMethods; ++i) {
691 const int index = api->sourceMethodIndex(index: i);
692 Q_ASSERT(index >= 0);
693 auto signature = api->methodSignature(index: i);
694 replace(signature);
695 const int count = api->methodParameterCount(index: i);
696 for (int pi = 0; pi < count; ++pi) {
697 const auto metaType = QMetaType(api->methodParameterType(methodIndex: i, paramIndex: pi));
698 if (isSequentialGadgetType(metaType))
699 signature.replace(before: metaType.name(), after: "QtROSequentialContainer");
700 else if (isAssociativeGadgetType(metaType))
701 signature.replace(before: metaType.name(), after: "QtROAssociativeContainer");
702 }
703 auto typeName = api->typeName(index: i);
704 replace(typeName);
705#ifdef QTRO_VERBOSE_PROTOCOL
706 qDebug() << " Slot" << i << "(signature =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")";
707#endif
708 ds << signature;
709 ds << typeName;
710 ds << api->methodParameterNames(index: i);
711 }
712
713 const int numProperties = api->propertyCount();
714 ds << quint32(numProperties); //Number of properties
715 for (int i = 0; i < numProperties; ++i) {
716 const int index = api->sourcePropertyIndex(index: i);
717 Q_ASSERT(index >= 0);
718
719 const auto target = api->isAdapterProperty(i) ? source->m_adapter : source->m_object;
720 const auto metaProperty = target->metaObject()->property(index);
721 ds << metaProperty.name();
722#ifdef QTRO_VERBOSE_PROTOCOL
723 qDebug() << " Property" << i << "name =" << metaProperty.name();
724#endif
725 if (metaProperty.metaType().flags().testFlag(flag: QMetaType::PointerToQObject)) {
726 auto objectType = getObjectType(typeName: QLatin1String(metaProperty.typeName()));
727 ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
728#ifdef QTRO_VERBOSE_PROTOCOL
729 qDebug() << " Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
730#endif
731 } else {
732 if (isSequentialGadgetType(metaType: metaProperty.metaType())) {
733 ds << "QtROSequentialContainer";
734#ifdef QTRO_VERBOSE_PROTOCOL
735 qDebug() << " Type:" << "QtROSequentialContainer";
736#endif
737 } else if (isAssociativeGadgetType(metaType: metaProperty.metaType())) {
738 ds << "QtROAssociativeContainer";
739#ifdef QTRO_VERBOSE_PROTOCOL
740 qDebug() << " Type:" << "QtROAssociativeContainer";
741#endif
742 } else {
743 ds << metaProperty.typeName();
744#ifdef QTRO_VERBOSE_PROTOCOL
745 qDebug() << " Type:" << metaProperty.typeName();
746#endif
747 }
748 }
749 if (metaProperty.notifySignalIndex() == -1) {
750 ds << QByteArray();
751#ifdef QTRO_VERBOSE_PROTOCOL
752 qDebug() << " Notification signal: None";
753#endif
754 } else {
755 auto signature = metaProperty.notifySignal().methodSignature();
756 replace(signature);
757 ds << signature;
758#ifdef QTRO_VERBOSE_PROTOCOL
759 qDebug() << " Notification signal:" << signature;
760#endif
761 }
762 }
763}
764
765void QDataStreamCodec::serializeAddObjectPacket(const QString &name, bool isDynamic)
766{
767 m_packet.setId(AddObject);
768 m_packet << name;
769 m_packet << isDynamic;
770 m_packet.finishPacket();
771}
772
773void QDataStreamCodec::deserializeAddObjectPacket(QDataStream &ds, bool &isDynamic)
774{
775 ds >> isDynamic;
776}
777
778void QDataStreamCodec::serializeRemoveObjectPacket(const QString &name)
779{
780 m_packet.setId(RemoveObject);
781 m_packet << name;
782 m_packet.finishPacket();
783}
784//There is no deserializeRemoveObjectPacket - no parameters other than id and name
785
786void QDataStreamCodec::serializeInvokePacket(const QString &name, int call, int index, const QVariantList &args, int serialId, int propertyIndex)
787{
788 m_packet.setId(InvokePacket);
789 m_packet << name;
790 m_packet << call;
791 m_packet << index;
792
793 m_packet << quint32(args.size());
794 for (const auto &arg : args)
795 m_packet << encodeVariant(value: arg);
796
797 m_packet << serialId;
798 m_packet << propertyIndex;
799 m_packet.finishPacket();
800}
801
802void QDataStreamCodec::deserializeInvokePacket(QDataStream& in, int &call, int &index, QVariantList &args, int &serialId, int &propertyIndex)
803{
804 in >> call;
805 in >> index;
806 const bool success = deserializeQVariantList(s&: in, l&: args);
807 Q_ASSERT(success);
808 Q_UNUSED(success)
809 in >> serialId;
810 in >> propertyIndex;
811}
812
813void QDataStreamCodec::serializeInvokeReplyPacket(const QString &name, int ackedSerialId, const QVariant &value)
814{
815 m_packet.setId(InvokeReplyPacket);
816 m_packet << name;
817 m_packet << ackedSerialId;
818 m_packet << value;
819 m_packet.finishPacket();
820}
821
822void QDataStreamCodec::deserializeInvokeReplyPacket(QDataStream& in, int &ackedSerialId, QVariant &value){
823 in >> ackedSerialId;
824 in >> value;
825}
826
827void QDataStreamCodec::serializePropertyChangePacket(QRemoteObjectSourceBase *source, int signalIndex)
828{
829 int internalIndex = source->m_api->propertyRawIndexFromSignal(index: signalIndex);
830 m_packet.setId(PropertyChangePacket);
831 m_packet << source->name();
832 m_packet << internalIndex;
833 serializeProperty(source, internalIndex);
834 m_packet.finishPacket();
835}
836
837void QDataStreamCodec::deserializePropertyChangePacket(QDataStream& in, int &index, QVariant &value)
838{
839 in >> index;
840 in >> value;
841}
842
843void QDataStreamCodec::serializeObjectListPacket(const ObjectInfoList &objects)
844{
845 m_packet.setId(ObjectList);
846 m_packet << objects;
847 m_packet.finishPacket();
848}
849
850void QDataStreamCodec::deserializeObjectListPacket(QDataStream &in, ObjectInfoList &objects)
851{
852 in >> objects;
853}
854
855void QDataStreamCodec::serializePingPacket(const QString &name)
856{
857 m_packet.setId(Ping);
858 m_packet << name;
859 m_packet.finishPacket();
860}
861
862void QDataStreamCodec::serializePongPacket(const QString &name)
863{
864 m_packet.setId(Pong);
865 m_packet << name;
866 m_packet.finishPacket();
867}
868
869QRO_::QRO_(QRemoteObjectSourceBase *source)
870 : name(source->name())
871 , typeName(source->m_api->typeName())
872 , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName))
873 , isNull(source->m_object == nullptr)
874 , classDefinition()
875 , parameters()
876{}
877
878QRO_::QRO_(const QVariant &value)
879 : type(ObjectType::GADGET)
880 , isNull(false)
881{
882 const auto metaType = value.metaType();
883 auto meta = metaType.metaObject();
884 QDataStream out(&classDefinition, QIODevice::WriteOnly);
885 const int numProperties = meta->propertyCount();
886 const auto name = metaType.name();
887 const auto typeName = QByteArray::fromRawData(data: name, size: qsizetype(qstrlen(str: name)));
888 out << quint32(0) << quint32(1);
889 out << typeName;
890 out << numProperties;
891#ifdef QTRO_VERBOSE_PROTOCOL
892 qDebug("Serializing POD definition to QRO_ (name = %s)", typeName.constData());
893#endif
894 for (int i = 0; i < numProperties; ++i) {
895 const auto property = meta->property(index: i);
896#ifdef QTRO_VERBOSE_PROTOCOL
897 qDebug(" Data member %d (name = %s, type = %s):", i, property.name(), property.typeName());
898#endif
899 out << QByteArray::fromRawData(data: property.name(), size: qsizetype(qstrlen(str: property.name())));
900 out << QByteArray::fromRawData(data: property.typeName(), size: qsizetype(qstrlen(str: property.typeName())));
901 }
902 int enumCount = meta->enumeratorCount() - meta->enumeratorOffset();
903 out << quint32(enumCount);
904 for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) {
905 auto const enumMeta = meta->enumerator(index: j);
906 serializeEnum(ds&: out, enumerator: enumMeta);
907 }
908 QDataStream ds(&parameters, QIODevice::WriteOnly);
909 ds << value;
910#ifdef QTRO_VERBOSE_PROTOCOL
911 qDebug() << " Value:" << value;
912#endif
913}
914
915QDataStream &operator<<(QDataStream &stream, const QRO_ &info)
916{
917 stream << info.name << info.typeName << quint8(info.type) << info.classDefinition << info.isNull;
918 qCDebug(QT_REMOTEOBJECT) << "Serializing " << info;
919 // info.parameters will be filled in by serializeProperty
920 return stream;
921}
922
923QDataStream &operator>>(QDataStream &stream, QRO_ &info)
924{
925 quint8 tmpType;
926 stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull;
927 info.type = static_cast<ObjectType>(tmpType);
928 qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info;
929 if (!info.isNull)
930 stream >> info.parameters;
931 return stream;
932}
933
934QSQ_::QSQ_(const QVariant &variant)
935{
936 QSequentialIterable sequence;
937 QMetaType valueType;
938 if (variant.metaType() == QMetaType::fromType<QtROSequentialContainer>()) {
939 auto container = static_cast<const QtROSequentialContainer *>(variant.constData());
940 typeName = container->m_typeName;
941 valueType = container->m_valueType;
942 valueTypeName = container->m_valueTypeName;
943 sequence = QSequentialIterable(reinterpret_cast<const QVariantList *>(variant.constData()));
944 } else {
945 sequence = variant.value<QSequentialIterable>();
946 typeName = QByteArray(variant.metaType().name());
947 valueType = sequence.metaContainer().valueMetaType();
948 valueTypeName = QByteArray(valueType.name());
949 }
950#ifdef QTRO_VERBOSE_PROTOCOL
951 qDebug("Serializing POD sequence to QSQ_ (type = %s, valueType = %s) with size = %lld",
952 typeName.constData(), valueTypeName.constData(), sequence.size());
953#endif
954 QDataStream ds(&values, QIODevice::WriteOnly);
955 ds << valueTypeName;
956 auto pos = ds.device()->pos();
957 ds << quint32(sequence.size());
958 for (const auto &v : sequence) {
959 if (!valueType.save(stream&: ds, data: v.data())) {
960 ds.device()->seek(pos);
961 ds.resetStatus();
962 ds << quint32(0);
963 values.resize(size: ds.device()->pos());
964 qWarning(msg: "QSQ_: unable to save type '%s', sending empty list.", valueType.name());
965 break;
966 }
967 }
968}
969
970QDataStream &operator<<(QDataStream &stream, const QSQ_ &sequence)
971{
972 stream << sequence.typeName << sequence.valueTypeName << sequence.values;
973 qCDebug(QT_REMOTEOBJECT) << "Serializing " << sequence;
974 return stream;
975}
976
977QDataStream &operator>>(QDataStream &stream, QSQ_ &sequence)
978{
979 stream >> sequence.typeName >> sequence.valueTypeName >> sequence.values;
980 qCDebug(QT_REMOTEOBJECT) << "Deserializing " << sequence;
981 return stream;
982}
983
984QAS_::QAS_(const QVariant &variant)
985{
986 QAssociativeIterable map;
987 QMetaType keyType, transferType, valueType;
988 const QtROAssociativeContainer *container = nullptr;
989 if (variant.metaType() == QMetaType::fromType<QtROAssociativeContainer>()) {
990 container = static_cast<const QtROAssociativeContainer *>(variant.constData());
991 typeName = container->m_typeName;
992 keyType = container->m_keyType;
993 keyTypeName = container->m_keyTypeName;
994 valueType = container->m_valueType;
995 valueTypeName = container->m_valueTypeName;
996 map = QAssociativeIterable(reinterpret_cast<const QVariantMap *>(variant.constData()));
997 } else {
998 map = variant.value<QAssociativeIterable>();
999 typeName = QByteArray(variant.metaType().name());
1000 keyType = map.metaContainer().keyMetaType();
1001 keyTypeName = QByteArray(keyType.name());
1002 valueType = map.metaContainer().mappedMetaType();
1003 valueTypeName = QByteArray(valueType.name());
1004 }
1005 // Special handling for enums...
1006 transferType = keyType;
1007 if (keyType.flags().testFlag(flag: QMetaType::IsEnumeration)) {
1008 transferType = transferTypeForEnum(enumType: keyType);
1009 auto meta = keyType.metaObject();
1010 // If we are the source, make sure the typeName is converted for any downstream replicas
1011 // the `meta` variable will be non-null when the enum is a class enum
1012 if (meta && !container) {
1013 const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
1014 if (ind >= 0) {
1015 bool isFlag = keyTypeName.startsWith(bv: "QFlags<");
1016 if (isFlag || keyTypeName.startsWith(bv: meta->className())) {
1017#ifdef QTRO_VERBOSE_PROTOCOL
1018 QByteArray orig(keyTypeName);
1019#endif
1020 if (isFlag) {
1021 // Q_DECLARE_FLAGS(Flags, Enum) -> `typedef QFlags<Enum> Flags;`
1022 // We know we have an enum for `Flags` because we sent the enums
1023 // from the source, so we just need to look up the alias.
1024 keyTypeName = keyTypeName.mid(index: 7);
1025 keyTypeName.chop(n: 1); // Remove trailing '>'
1026 int index = keyTypeName.lastIndexOf(c: ':');
1027 for (int i = meta->enumeratorOffset(); i < meta->enumeratorCount(); i++) {
1028 auto en = meta->enumerator(index: i);
1029 auto name = keyTypeName.data() + index + 1;
1030 if (en.isFlag() && qstrcmp(str1: en.enumName(), str2: name) == 0)
1031 keyTypeName.replace(index: index + 1, len: qstrlen(str: en.enumName()), s: en.name());
1032 }
1033 }
1034 keyTypeName.replace(before: meta->className(), after: meta->classInfo(index: ind).value());
1035 QByteArray repName(meta->classInfo(index: ind).value());
1036 repName.append(s: "Replica");
1037 typeName.replace(before: meta->className(), after: repName);
1038#ifdef QTRO_VERBOSE_PROTOCOL
1039 qDebug() << "Converted map key typename from" << orig << "to" << keyTypeName;
1040#endif
1041 }
1042 }
1043 }
1044 }
1045
1046#ifdef QTRO_VERBOSE_PROTOCOL
1047 qDebug("Serializing POD map to QAS_ (type = %s, keyType = %s, valueType = %s), size = %lld",
1048 typeName.constData(), keyTypeName.constData(), valueTypeName.constData(),
1049 map.size());
1050#endif
1051 QDataStream ds(&values, QIODevice::WriteOnly);
1052 ds << keyTypeName;
1053 ds << valueTypeName;
1054 auto pos = ds.device()->pos();
1055 ds << quint32(map.size());
1056 QAssociativeIterable::const_iterator iter = map.begin();
1057 for (int i = 0; i < map.size(); i++) {
1058 QVariant key(container ? container->m_keys.at(i) : iter.key());
1059 if (transferType != keyType)
1060 key.convert(type: transferType);
1061 if (!transferType.save(stream&: ds, data: key.data())) {
1062 ds.device()->seek(pos);
1063 ds.resetStatus();
1064 ds << quint32(0);
1065 values.resize(size: ds.device()->pos());
1066 qWarning(msg: "QAS_: unable to save key '%s', sending empty map.", keyType.name());
1067 break;
1068 }
1069 if (!valueType.save(stream&: ds, data: iter.value().data())) {
1070 ds.device()->seek(pos);
1071 ds.resetStatus();
1072 ds << quint32(0);
1073 values.resize(size: ds.device()->pos());
1074 qWarning(msg: "QAS_: unable to save value '%s', sending empty map.", valueType.name());
1075 break;
1076 }
1077 iter++;
1078 }
1079}
1080
1081QDataStream &operator<<(QDataStream &stream, const QAS_ &map)
1082{
1083 stream << map.typeName << map.keyTypeName << map.valueTypeName << map.values;
1084 qCDebug(QT_REMOTEOBJECT) << "Serializing " << map;
1085 return stream;
1086}
1087
1088QDataStream &operator>>(QDataStream &stream, QAS_ &map)
1089{
1090 stream >> map.typeName >> map.keyTypeName >> map.valueTypeName >> map.values;
1091 qCDebug(QT_REMOTEOBJECT) << "Deserializing " << map;
1092 return stream;
1093}
1094
1095DataStreamPacket::DataStreamPacket(quint16 id)
1096 : QDataStream(&array, QIODevice::WriteOnly), baseAddress(0), size(0)
1097{
1098 this->setVersion(QtRemoteObjects::dataStreamVersion);
1099 this->setByteOrder(QDataStream::LittleEndian);
1100 *this << quint32(0);
1101 *this << id;
1102}
1103
1104void CodecBase::send(const QSet<QtROIoDeviceBase *> &connections)
1105{
1106 const auto bytearray = getPayload();
1107 for (auto conn : connections)
1108 conn->write(data: bytearray);
1109 reset();
1110}
1111
1112void CodecBase::send(const QList<QtROIoDeviceBase *> &connections)
1113{
1114 const auto bytearray = getPayload();
1115 for (auto conn : connections)
1116 conn->write(data: bytearray);
1117 reset();
1118}
1119
1120void CodecBase::send(QtROIoDeviceBase *connection)
1121{
1122 const auto bytearray = getPayload();
1123 connection->write(data: bytearray);
1124 reset();
1125}
1126
1127} // namespace QRemoteObjectPackets
1128
1129QT_IMPL_METATYPE_EXTERN_TAGGED(QRemoteObjectPackets::QRO_, QRemoteObjectPackets__QRO_)
1130
1131QT_END_NAMESPACE
1132

source code of qtremoteobjects/src/remoteobjects/qremoteobjectpacket.cpp