Back to Home

Serialization in Qt through the use of MetaObject

Qt · serialization · QMetaObject · QVariant · QMetaType

Serialization in Qt through the use of MetaObject

    Background


    Actually, why would you need this? After all, C ++ already provides quite flexible options for serialization into a stream. However, I had the task of maximally universalizing the serialization / deserialization process for repeated use in projects.

    So, it was necessary to organize the most flexible (de) serialization system in Qt as possible, so that you could
    1. either inheriting from the base class and expanding it
    2. either having a separate serializer class
    be able to send a stream of data from an object with one command.

    At the same time, in some way, it was necessary to be able to indicate which data in the object should be serialized, and which could (and should) be “skipped”. Likewise, when deserializing, the ability to correctly set the data and related dependent values ​​inside the object should have been performed.

    Decision



    The solution was found as a property system. Qt has add-ons over C ++ to provide a property system. We have the right to use properties only in classes inherited from QObject with the Q_OBJECT tag. Assigning properties occurs when using a macro of this kind when describing a class in a header (example):
    Q_PROPERTY(int Test READ readTest WRITE setTest)

    As you can see from the example, you can assign a read (readTest) and a write (setTest) function. The latter ensures that the deserialization is correct.

    The parameters necessary for serialization are typed in the object in the form of properties, thereby specifying a list of properties necessary to ensure the integrity of the class data.

    To provide access to properties in a general way (given that the base class about the class that passed itself to serialization does not know anything) can be obtained through the MetaObject system (describing an object with its property system and generated for any object) MetaProperty, which provide a description and access to properties. The result is a code for (de) serialization:
    bool SerializedBase::Serialize(QDataStream *dataStream)
    {
      if (dataStream == NULL)
        return false;
      for (int i = 1; i < this->metaObject()->propertyCount(); i++)
      {
         QMetaProperty prop = this->metaObject()->property(i);
         const char* propName = prop.name();
         *dataStream << (this->property(propName));
      }
      return true;
    }

    bool SerializedBase::DeSerialize(QDataStream *dataStream)
    {
      if (dataStream == NULL)
        return false;
      for (int i = 1; i < this->metaObject()->propertyCount(); i++)
      {
         QMetaProperty prop = this->metaObject()->property(i);
         const char* propName = prop.name();
         QVariant v;
         *dataStream >> v;
         this->setProperty(propName, v);
      }
      return true;
    }

    * This source code was highlighted with Source Code Highlighter.

    As a result, we got a base class, having inherited from which we can serialize / deserialize an object in 1 command (if there is any data stream created).

    The code for a freestanding class should be almost identical, except that add. checks in order to understand the object in front of us or the usual data structure).

    Additional information and modifications



    Objects in the form of properties and their serialization

    The Q_DISABLE_COPY directive in the QObject class code does not allow you to assign object values ​​directly to properties. In order to provide the necessary flexibility, you need to perform 3 actions:
    1. Create a copy constructor. Let's say if the object AAA class, the constructor should look like this (in the file header) AAA(AAA* copy). It actually defines actions when copying an object.
    2. Register a metaclass in the class header. It is done like this:Q_DECRARE_METATYPE(AAA);
    3. When using the property so as not to “frustrate” the problems (usually they are still defined at the compiler level, so don’t miss it), you need to register for the class somewhere in the application through the command You qRegisterMetaType("AAA")
      must re-assign the streaming for the classes used, that is, specify how to send them into the stream. It is done through the team qRegisterMetaTypeStreamOperators ( const char * typeName ). Description of the functions that must be implemented in the class:

      QDataStream &operator<<(QDataStream &out, const MyClass &myObj);
      QDataStream &operator>>(QDataStream &in, MyClass &myObj);


    After all these actions, we will be able to operate with object properties, not only the values ​​of standard types, and not use the references to these types of types (see drawbacks).

    Using dynamically created instances

    The Qt system of properties supports this, though again, not “directly”, it is necessary to perform body movements. The class (base) must meet the requirements in the list above (well, unless item 4 is already optional), plus another (thanks for the hint fuCtor ):
    1. The constructor used must have the Q_INVOKABLE macro in its description
    2. Use the QMetaObject :: newInstance ( ) , clinging to the class name that can be passed along with the object (or its identifier) ​​QMetaObject. If there is no way to get MetaObject ready, you will have to be content with the default constructor (which nevertheless must satisfy the same requirements, and use QMetaType :: construct, before receiving by the name Id of the registered type through int QMetaType::type ( const char * typeName ), and by Id (not forgetting to check whether the type is registered in the system by comparing with 0) MetaType itself.

    Advantages and disadvantages


    Briefly outline the benefits of this method:
    • Quick adaptation to new classes - write properties, functions for receiving and installing them, inherit - and voila, serialization works
    • A system of both static and dynamic properties is supported. If in the client-server system at that end, during deserialization, the object does not support properties with a definition. by name - it is created dynamically, and it can be tracked anyway (though the setter / getter doesn’t appear out of nowhere. We won’t lose data either)
    • It is working :)


    But the shortcomings identified at the moment:
    • Inability to correctly pass property-links. Addresses are transmitted, but the data behind the links is not. Physically, nobody forbids us to do pointer properties, however serialization will not work correctly with them
    • Custom property types must be registered with Q_DECLARE_METATYPE in order to be able to operate on them through QVariant
    • If you try to use the objects themselves in terms of properties, you need to write a lot of additional code in the class - constructors, macros, etc. True, in the end it translates into great versatility.

    Read Next