From c2165f891c1a4380fdbf3bbf7af4696dc863cb99 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Wed, 22 Aug 2018 15:03:12 +0300 Subject: Allow registering constructor for a set of dynamic types This feature is needed to register complex types (e.g. Q_GADGETS) at runtime, using a single constructor method and added type parameter. Without having the type id available to the Constructor it is impossible to specialize behavior, thus requiring separate constructors for each type. Generating these separate constructors at compile time is easy, but not at runtime. [ChangeLog][QMetaType] QMetaType can now register constructor for a set of dynamic types. Change-Id: I6071271d0e157864594dd07b4bc3a0abbeb15a44 Reviewed-by: Thiago Macieira --- tests/auto/corelib/kernel/qmetatype/qmetatype.pro | 2 +- .../corelib/kernel/qmetatype/tst_qmetatype.cpp | 246 +++++++++++++++++++++ 2 files changed, 247 insertions(+), 1 deletion(-) (limited to 'tests/auto') diff --git a/tests/auto/corelib/kernel/qmetatype/qmetatype.pro b/tests/auto/corelib/kernel/qmetatype/qmetatype.pro index ad148ccc7f..d70befecfd 100644 --- a/tests/auto/corelib/kernel/qmetatype/qmetatype.pro +++ b/tests/auto/corelib/kernel/qmetatype/qmetatype.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qmetatype -QT = core testlib +QT = core-private testlib INCLUDEPATH += $$PWD/../../../other/qvariant_common SOURCES = tst_qmetatype.cpp TESTDATA=./typeFlags.bin diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp index 73618db3f4..fdf3bb541e 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include #include "tst_qmetatype.h" #include "tst_qvariant_common.h" @@ -38,6 +40,7 @@ #endif #include +#include // mingw gcc 4.8 also takes way too long, letting the CI system abort the test #if defined(__MINGW32__) @@ -52,12 +55,19 @@ class tst_QMetaType: public QObject Q_PROPERTY(QList prop READ prop WRITE setProp) public: + struct GadgetPropertyType { + QByteArray type; + QByteArray name; + QVariant testData; + }; + tst_QMetaType() { propList << 42 << "Hello"; } QList prop() const { return propList; } void setProp(const QList &list) { propList = list; } private: + void registerGadget(const char * name, const QVector &gadgetProperties); QList propList; private slots: @@ -89,6 +99,7 @@ private slots: void flagsBinaryCompatibility5_0(); void construct_data(); void construct(); + void typedConstruct(); void constructCopy_data(); void constructCopy(); void typedefs(); @@ -115,6 +126,128 @@ private slots: void customDebugStream(); }; +struct BaseGenericType +{ + int m_typeId = -1; + virtual void *constructor(int typeId, void *where, const void *copy) = 0; + virtual void staticMetacallFunction(QMetaObject::Call _c, int _id, void **_a) = 0; + virtual void saveOperator(QDataStream & out) const = 0; + virtual void loadOperator(QDataStream &in) = 0; + virtual ~BaseGenericType() {} +}; + +struct GenericGadgetType : BaseGenericType +{ + void *constructor(int typeId, void *where, const void *copy) override + { + GenericGadgetType *ret = where ? new(where) GenericGadgetType : new GenericGadgetType; + ret->m_typeId = typeId; + if (copy) { + Q_ASSERT(ret->m_typeId == reinterpret_cast(copy)->m_typeId); + *ret = *reinterpret_cast(copy); + } else { + ret->properties = properties; + } + return ret; + } + + void staticMetacallFunction(QMetaObject::Call _c, int _id, void **_a) override + { + if (_c == QMetaObject::ReadProperty) { + if (_id < properties.size()) { + const auto &prop = properties.at(_id); + QMetaType::destruct(int(prop.userType()), _a[0]); + QMetaType::construct(int(prop.userType()), _a[0], prop.constData()); + } + } else if (_c == QMetaObject::WriteProperty) { + if (_id < properties.size()) { + auto & prop = properties[_id]; + prop = QVariant(prop.userType(), _a[0]); + } + } + } + + void saveOperator(QDataStream & out) const override + { + for (const auto &prop : properties) + out << prop; + } + + void loadOperator(QDataStream &in) override + { + for (auto &prop : properties) + in >> prop; + } + QVector properties; +}; + +struct GenericPODType : BaseGenericType +{ + // BaseGenericType interface + void *constructor(int typeId, void *where, const void *copy) override + { + GenericPODType *ret = where ? new(where) GenericPODType : new GenericPODType; + ret->m_typeId = typeId; + if (copy) { + Q_ASSERT(ret->m_typeId == reinterpret_cast(copy)->m_typeId); + *ret = *reinterpret_cast(copy); + } else { + ret->podData = podData; + } + return ret; + } + + void staticMetacallFunction(QMetaObject::Call _c, int _id, void **_a) override + { + Q_UNUSED(_c); + Q_UNUSED(_id); + Q_UNUSED(_a); + Q_ASSERT(false); + } + + void saveOperator(QDataStream &out) const override + { + out << podData; + } + void loadOperator(QDataStream &in) override + { + in >> podData; + } + QByteArray podData; +}; + +using RegisteredType = QPair, std::shared_ptr>; +static QHash s_managedTypes; + +static void GadgetsStaticMetacallFunction(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + reinterpret_cast(_o)->staticMetacallFunction(_c, _id, _a); +} + +static void GadgetTypedDestructor(int typeId, void *ptr) +{ + QCOMPARE(typeId, reinterpret_cast(ptr)->m_typeId); + reinterpret_cast(ptr)->~BaseGenericType(); +} + +static void *GadgetTypedConstructor(int type, void *where, const void *copy) +{ + auto it = s_managedTypes.find(type); + if (it == s_managedTypes.end()) + return nullptr; // crash the test + return it->first->constructor(type, where, copy); +} + +static void GadgetSaveOperator(QDataStream & out, const void *data) +{ + reinterpret_cast(data)->saveOperator(out); +} + +static void GadgetLoadOperator(QDataStream &in, void *data) +{ + reinterpret_cast(data)->loadOperator(in); +} + struct Foo { int i; }; @@ -149,6 +282,35 @@ class GadgetDerivedAndTyped : public CustomGadget {}; Q_DECLARE_METATYPE(GadgetDerivedAndTyped) Q_DECLARE_METATYPE(GadgetDerivedAndTyped*) +void tst_QMetaType::registerGadget(const char *name, const QVector &gadgetProperties) +{ + QMetaObjectBuilder gadgetBuilder; + gadgetBuilder.setClassName(name); + QMetaObjectBuilder::MetaObjectFlags metaObjectflags = QMetaObjectBuilder::DynamicMetaObject; + metaObjectflags |= QMetaObjectBuilder::MetaObjectFlags(PropertyAccessInStaticMetaCall); + gadgetBuilder.setFlags(metaObjectflags); + auto dynamicGadgetProperties = std::make_shared(); + for (const auto &prop : gadgetProperties) { + int propertyType = QMetaType::type(prop.type); + dynamicGadgetProperties->properties.push_back(QVariant(QVariant::Type(propertyType))); + auto dynamicPropery = gadgetBuilder.addProperty(prop.name, prop.type); + dynamicPropery.setWritable(true); + dynamicPropery.setReadable(true); + } + auto meta = gadgetBuilder.toMetaObject(); + meta->d.static_metacall = &GadgetsStaticMetacallFunction; + meta->d.superdata = nullptr; + const auto flags = QMetaType::WasDeclaredAsMetaType | QMetaType::IsGadget | QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + int gadgetTypeId = QMetaType::registerType(name, + &GadgetTypedDestructor, + &GadgetTypedConstructor, + sizeof(GenericGadgetType), + flags, meta); + QVERIFY(gadgetTypeId > 0); + QMetaType::registerStreamOperators(gadgetTypeId, &GadgetSaveOperator, &GadgetLoadOperator); + s_managedTypes[gadgetTypeId] = qMakePair(dynamicGadgetProperties, std::shared_ptr{meta, [](QMetaObject *ptr){ ::free(ptr); }}); +} + void tst_QMetaType::defined() { QCOMPARE(int(QMetaTypeId2::Defined), 1); @@ -906,6 +1068,90 @@ FOR_EACH_CORE_METATYPE(RETURN_CONSTRUCT_FUNCTION) TypeTestFunctionGetter::get(type)(); } +void tst_QMetaType::typedConstruct() +{ + auto testMetaObjectWriteOnGadget = [](QVariant &gadget, const QVector &properties) + { + auto metaObject = QMetaType::metaObjectForType(gadget.userType()); + QVERIFY(metaObject != nullptr); + QCOMPARE(metaObject->methodCount(), 0); + QCOMPARE(metaObject->propertyCount(), properties.size()); + for (int i = 0; i < metaObject->propertyCount(); ++i) { + auto prop = metaObject->property(i); + QCOMPARE(properties[i].name, prop.name()); + QCOMPARE(properties[i].type, prop.typeName()); + prop.writeOnGadget(gadget.data(), properties[i].testData); + } + }; + + auto testMetaObjectReadOnGadget = [](QVariant gadget, const QVector &properties) + { + auto metaObject = QMetaType::metaObjectForType(gadget.userType()); + QVERIFY(metaObject != nullptr); + QCOMPARE(metaObject->methodCount(), 0); + QCOMPARE(metaObject->propertyCount(), properties.size()); + for (int i = 0; i < metaObject->propertyCount(); ++i) { + auto prop = metaObject->property(i); + QCOMPARE(properties[i].name, prop.name()); + QCOMPARE(properties[i].type, prop.typeName()); + if (!QMetaType::typeFlags(prop.userType()).testFlag(QMetaType::IsGadget)) + QCOMPARE(properties[i].testData, prop.readOnGadget(gadget.constData())); + } + }; + + QVector dynamicGadget1 = { + {"int", "int_prop", 34526}, + {"float", "float_prop", 1.23f}, + {"QString", "string_prop", QString{"Test QString"}} + }; + registerGadget("DynamicGadget1", dynamicGadget1); + + QVariant testGadget1(QVariant::Type(QMetaType::type("DynamicGadget1"))); + testMetaObjectWriteOnGadget(testGadget1, dynamicGadget1); + testMetaObjectReadOnGadget(testGadget1, dynamicGadget1); + + + QVector dynamicGadget2 = { + {"int", "int_prop", 512}, + {"double", "double_prop", 4.56}, + {"QString", "string_prop", QString{"Another String"}}, + {"DynamicGadget1", "dynamicGadget1_prop", testGadget1} + }; + registerGadget("DynamicGadget2", dynamicGadget2); + QVariant testGadget2(QVariant::Type(QMetaType::type("DynamicGadget2"))); + testMetaObjectWriteOnGadget(testGadget2, dynamicGadget2); + testMetaObjectReadOnGadget(testGadget2, dynamicGadget2); + auto g2mo = QMetaType::metaObjectForType(testGadget2.userType()); + auto dynamicGadget1_prop = g2mo->property(g2mo->indexOfProperty("dynamicGadget1_prop")); + testMetaObjectReadOnGadget(dynamicGadget1_prop.readOnGadget(testGadget2.constData()), dynamicGadget1); + + + // Register POD + const QByteArray myPodTesData = "My POD test data"; + const char podTypeName[] = "DynamicPOD"; + auto dynamicGadgetProperties = std::make_shared(); + dynamicGadgetProperties->podData = myPodTesData; + const auto flags = QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + int podTypeId = QMetaType::registerType(podTypeName, + &GadgetTypedDestructor, + &GadgetTypedConstructor, + sizeof(GenericGadgetType), + flags, nullptr); + QVERIFY(podTypeId > 0); + QMetaType::registerStreamOperators(podTypeId, &GadgetSaveOperator, &GadgetLoadOperator); + s_managedTypes[podTypeId] = qMakePair(dynamicGadgetProperties, std::shared_ptr{}); + + // Test POD + QCOMPARE(podTypeId, QMetaType::type(podTypeName)); + QVariant podVariant{QVariant::Type(podTypeId)}; + QCOMPARE(myPodTesData, static_cast(reinterpret_cast(podVariant.constData()))->podData); + + QVariant podVariant1{podVariant}; + podVariant1.detach(); // Test stream operators + static_cast(reinterpret_cast(podVariant.data()))->podData.clear(); + QCOMPARE(myPodTesData, static_cast(reinterpret_cast(podVariant1.constData()))->podData); +} + template static void testConstructCopyHelper() { -- cgit v1.2.3