Сериализация объектов Qt
Здесь меня будет интересовать как сериализовать объект Qt, передать его по сети и поддерживать между оригиналом и копией связь синхронизирующую состояние копии. Использовался Qt 4.8.Например, мне нужен был объект содержащий несколько свойств и сигналовкласс Deviceсвойство nameсвойство stateсвойство numberсигнал alarm ()
Если я создаю объект класса Device, то такой же объект должен появится на удаленной стороне. Если я меняю свойство name объекта, то оно меняется на удаленной стороне. И никаких дополнительных действий для синхронизации не предпринимается.
Для реализации такого поведения объекта нам потребуется, очевидно, связка клиент-сервер произвольной реализации (здесь этот вопрос мы не рассмотрим) и некоторые хитрости Qt.
1. Опишем класс UserMetaObject
#define Q_CONSTRUCTOR (type) \
public: \
type (const ArgMap& arg = ArgMap ()) { set (arg); super (arg); init
class SERIALOBJECT_EXPORT UserMetaObject: public QObject { Q_OBJECT int objectID; // идентификатор объекта
protected: static int objectIDCounter; // статический счетчик для идентификаторов QString cname; // имя класса создаваемого объекта
public: UserMetaObject (); virtual ~UserMetaObject (); void setObjectID (int value); int getObjectID () const; QString className () const; }; Идентификатор объекта — это уникальный номер, благодаря которому все отсылаемые параметры объекта вроде сигналов и свойств дойдут до нужного объекта на удаленной стороне.Макрос Q_CONSTRUCTOR — служит для гарантированной регистрации Qt-объектов в списках метатипов и запрещает конструктор копирования.Остальное очевидно, кроме ArgMap — это список первоначальной инициализации свойств объекта.
2. Опишем класс UserMetaCall наследуемый от UserMetaObject
class UserMetaCall: public UserMetaObject
{
QList
protected: void set (const ArgMap &arg);
public: explicit UserMetaCall (); virtual ~UserMetaCall (); int qt_metacall (QMetaObject: Call call, int id, void ** arg); bool findSuitDetector (SignalDetector *det, const QMetaMethod& met); bool isPropertySignal (int signalID); bool setDefaultDetector (SignalDetector *det); void collectSignals ();
template
void UserMetaCall: collectSignals ()
{
for (size_t i=metaObject ()→methodOffset (); i
Поиск подходящего слота может происходить так
bool UserMetaCall: findSuitDetector (SignalDetector *det, const QMetaMethod &met)
{
if (! det) return false;
for (size_t method=det→metaObject ()→methodOffset ();
method
bool UserMetaCall: setDefaultDetector (SignalDetector *det)
{
if (! det) return false;
int defaultMethId = det→metaObject ()→indexOfMethod («onSignal ()»);
int sigID = metaObject ()→indexOfMethod (det→getSignature ().toStdString ().c_str ());
if (! QMetaObject: connect (this, sigID, det, defaultMethId))
{
qWarning ()<<"connect fail";
return false;
}
return true;
}
В случае отсутствия подходящего слота (чего быть не должно) пытаемся использовать слот onSignal().
bool UserMetaCall::isPropertySignal(int signalID)
{
for(size_t i=metaObject()->propertyOffset ();
i
Примерная реализация сигнального детектора может иметь такой вид
class SignalDetector: public QObject { Q_OBJECT QString signature; int objectID; void process (const QVariant &value);
public: SignalDetector (const QString &signame, QObject *parent =0): QObject (parent), signature (signame){} int getObjectID () const; void setObjectID (int value); QString getSignature ();
public Q_SLOTS: void onSignal (QString value) { process (QVariant (value)); } void onSignal (int value) { process (QVariant (value)); } void onSignal (bool value) { process (QVariant (value)); } void onSignal (float value) { process (QVariant (value)); } void onSignal (double value) { process (QVariant (value)); } void onSignal (char value) { process (QVariant (value)); } void onSignal () { process (QVariant ()); } }; Тут, я думаю, все понятно. Функция process отсылает данные через клиента удаленной стороне.
4. Начальная инициализация свойств
Как мы упоминали выше, за это отвечает функция set
void UserMetaCall: set (const ArgMap &arg) { for (QPropertyList: const_iterator i=arg.begin (); i!=arg.end (); ++i) { int propNumber = metaObject ()→indexOfProperty (i.key ().toStdString ().c_str ()); if (propNumber>=0) { setProperty (i.key ().toStdString ().c_str (), i.value ()); } } } Элементарно, мы ищем свойства к подходящими именами и устанавливаем им значения. (Заметим, это нужно сделать до регистрации объекта в клиенте, чтоб тот не пытался передавать изменения свойств объекта, который еще не создан на противоположной стороне).
Была еще функция super, но она виртуальная и реализации не имеет, если честно. Каждый пользователь UserMetaCall-классов может переопределить ее, если хочет поработать со свойствами в «конструкторе» каким-то особенным образом.
5. Передача свойств
Если внимательно присмотреться к классу QObject, то в макроопределении Q_OBJECT можно найти такие строчки
#define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ Q_OBJECT_GETSTATICMETAOBJECT \ virtual const QMetaObject *metaObject () const; \ virtual void *qt_metacast (const char *); \ QT_TR_FUNCTIONS \ virtual int qt_metacall (QMetaObject: Call, int, void **); \ private: \ Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \ Q_DECL_HIDDEN static void qt_static_metacall (QObject *, QMetaObject: Call, int, void **); … Нас интересует строка virtual void *qt_metacast (const char *); Именно через эту функцию проходят такие вызовы как: QMetaObject: WritePropertyQMetaObject: ReadProperty
Нас будет интересовать только изменение свойства объекта, поэтому сосредоточимся только на QMetaObject: WriteProperty.Поскольку qt_metacast функция виртуальная, сделаем «подмену» для нее, чтоб выловить все изменения свойства.
В классе UserMetaCall определим функцию
int UserMetaCall: qt_metacall (QMetaObject: Call call, int id, void **arg)
{
if (call == QMetaObject: WriteProperty)
{
QVariant var = (*reinterpret_cast
6. Не забываем про удаление! Удаление объекта должно влечь за собой его удаление на серверной стороне. Однако этот вопрос имеет свои нюансы. Нельзя просто так взять, и удалить объект! Это небезопасно. Объект на серверной части может находится в обработке, может использоваться в то время, когда вам вздумалось удалить его здесь. Поэтому при вызове деструктора мы передаем, конечно, информацию о том, что объект был уничтожен серверу, но на той стороне должны сами решать, что делать с копией без оригинала.
Вот, собственно и все. В общих чертах, конечно.Свой класс можно описывать так
class Device: public UserMetaCall { Q_OBJECT Q_PROPERTY (QString name READ name WRITE setname NOTIFY nameChanged) Q_PROPERTY (bool state READ state WRITE setstate NOTIFY stateChanged) Q_PROPERTY (int number READ number WRITE setnumber NOTIFY numberChanged) Q_CONSTRUCTOR (Device)
public: void super (const ArgMap &arg); QString name () const; bool state () const; int number () const; void callAlarm ();
public slots: void setname (QString arg); void setstate (bool arg); void setnumber (int arg);
signals: void nameChanged (QString arg); void stateChanged (bool arg); void numberChanged (int arg); void alarm ();
private: QString m_name; bool m_state; int m_number; }; После обработки Qt moc файл будет сгенерирован со следующим содержимым
int Device: qt_metacall (QMetaObject: Call _c, int _id, void **_a) { _id = UserMetaCall: qt_metacall (_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { if (_id < 7) qt_static_metacall(this, _c, _id, _a); _id -= 7; } #ifndef QT_NO_PROPERTIES else if (_c == QMetaObject::ReadProperty) { ... Видите, сначала он обращается к родительской функции UserMetaCall::qt_metacall которую мы переопределили.Общая схема получилась такой. Мы создаем объект-наследник UserMetaCall, он автоматически получает идентификатор и клиентская часть связывается с серверной, на удаленной стороне создается копия объекта с соответствующим идентификатором. Далее любое изменение свойства объекта тут же сказывается на копии, вызов сигнала приводит к аналогичному поведению у копии. То есть наш клиент незаметно для пользователя отсылает уведомления с идентификатором, именем свойства или сигнала и QVariant-ом — значением свойства или аргументами сигнала.
Принцип работы серверной части, имхо, понятен. Принять информацию от клиента и обработать правильно. Так же используется metaObject, но уже никаких хитростей.Данный метод работает в Qt4, не проверял на Qt5, но вроде бы qt_metacall там на месте. А вот с Qt3 были некоторые сложности, поскольку там система немного иная, приходилось немного пошаманить с pro фалом для достижения аналогичного результата. Но, скорее всего, никто уже не пишет на Qt3, это неинтересно.