Qt Meta System over Network. Часть 2 — сигналы и слоты

от автора


Часть 1 — Свойства
Продолжаем разбираться с метасистемой Qt. В этот раз рассмотрим создание виртуальных сигналов и слотов.

1. Сигналы

А точнее подключение к любым сигналам. Тут всё очень похоже на работу со свойствами, только аргументов может быть от 0 до 10. Цель — получить класс DynamicQObject, который сможет подключиться к любому сигналу и при его активации, вызвать некоторый метод с именем сигнала и переданными аргументами в виде QVariantList. Также буду разбивать код на кусочки с комментариями, приступим:

dynamicqobject.h

class DynamicQObject : public QObject { public:     DynamicQObject(QObject *mapToObject,                    const char *signalCatchMethod,                    QObject *parent = 0);      bool addSlot(QObject *object,                  const char *signal,                  const QString &slotName);     bool removeSlot(const QString &name);      bool addSignal(const QString &name, QObject *object, const char *slot);     bool removeSignal(const QString &name);     bool activate(const QString &signalName, const QVariantList &args);      int qt_metacall(QMetaObject::Call call, int id, void **arguments); private:     // virtual slots     bool containsSlot(const QString &name);     QObject *m_mapTo;     const char *m_catchMethod;     typedef struct {         bool isEmpty; // true after removeSlot()         QObject *object;         int signalIdx;         QString name; // virtual slot name         QVector<int> parameterTypes;     } slot_t;     QVector<slot_t> m_slotList;      // virtual signals     typedef struct {         bool isEmpty; // // true after removeSignal()         QObject *reciever;         int slotIdx;         QString name;         QVector<int> parameterTypes;     } signal_t;     QVector<signal_t> m_signalList;     QHash<QString, int> m_signalHash;     void *m_parameters[11]; // max 10 parameters + ret value }; 

addSlot — создаёт новый виртуальный слот с именем slotName и подключает его к сигналу signal объекта object. Посмотрим как тут всё устроено:

bool DynamicQObject::addSlot(QObject *object,                              const char *signal,                              const QString &slotName) {     if (containsSlot(slotName))         return false;     if (signal[0] != '2') {         qWarning() << "Use SIGNAL() macro";         return false;     } 

Проверяем нет ли уже слота с таким же именем, а также наличие символа ‘2’ в начале его имени, этот символ добавляет макрос SIGNAL().

    QByteArray theSignal = QMetaObject::normalizedSignature(&signal[1]);     int signalId = object->metaObject()->indexOfSignal(theSignal);     if (signalId < 0) {         qWarning() << "signal" << signal << "doesn't exist";         return false;     }      QVector<int> parameterTypes;     QMetaMethod signalMethod = object->metaObject()->method(signalId);     for (int i = 0; i < signalMethod.parameterCount(); ++i)         parameterTypes.push_back(signalMethod.parameterType(i)); 

Также как и в прошлый раз, получаем индекс сигнала, а далее получаем по этому индексу QMetaMethod. Из которого мы по одному получаем аргументы сигнала, создаём из них вектор. Далее мы будем приводить полученные аргументы именно к этим типам.

    int slotIdx = -1;     for (int i = 0; i < m_slotList.count(); ++i) {         if (m_slotList[i].isEmpty == true) {             slotIdx = i;             break;         }     }     bool addEntry = false;     if (slotIdx == -1) {         addEntry = true;         slotIdx = m_slotList.count();     } 

Здесь всё совсем просто, находим пустую запись (либо создаём новую) в m_slotList, куда мы сохраним информацию о созданном слоте. Пустые записи образуются после удаления слотов ( removeSlot() ), т.к. индексы у нас привязаны к слотам, их сдвиг привёл бы к вызову неверных слотов. Можно было бы применить здесь QHash или QMap, но я посчитал, что удаляют слоты гораздо реже, чем создают, а вызывают очень часто, так что вектор явно эффективнее, т.к. у него доступ по индексу выполняется за O(1), а у QMap и QHash в худшем случае за O(logn) и O(n) соответственно.
Собственно осталось только подключиться к сигналу:

    if (!QMetaObject::connect(object, signalId,                                 this, slotIdx + metaObject()->methodCount())) {         qWarning() << "can't connect" << signal << "signal to virtual slot";         return false;     }      if (addEntry) {         m_slotList.push_back({false, object, signalId, slotName, parameterTypes});     } else {         slot_t &slot = m_slotList[slotIdx];         slot.isEmpty = false;         slot.object = object;         slot.signalIdx = signalId;         slot.name = slotName;         slot.parameterTypes = parameterTypes;     }      return true; } 

И сохранить всю необходимую информацию о нём.

Вызов слота

Тут всё похоже на фокус со свойствами, создаём свою реализацию qt_metacall, только у нас теперь аргументов может быть больше:

int DynamicQObject::qt_metacall(QMetaObject::Call call, int id, void **arguments) {     id = QObject::qt_metacall(call, id, arguments);     if (id < 0 || call != QMetaObject::InvokeMetaMethod)         return id;     Q_ASSERT(id < m_slotList.size()); 

Проверяем, что вызывают действительно слот (метаметод), а также что индекс корректный.

    const slot_t &slotInfo = m_slotList[id];     QVariantList parameters;     for (int i = 0; i < slotInfo.parameterTypes.count(); ++i) {         void *parameter = arguments[i + 1];         parameters.append(QVariant(slotInfo.parameterTypes[i], parameter));     } 

Достаём ранее сохранённую информацию о слоте. И дальше создаём список QVariant-ов из полученный аргументов и сохранённых типов.

    QMetaObject::invokeMethod(m_mapTo, m_catchMethod,                               Q_ARG(QString, slotInfo.name),                               Q_ARG(QVariantList, parameters));     return -1; } 

Осталось только вызвать указанный в конструкторе метод с именем слота, и аргументами.

В removeSlot() нет ничего интересного, так что посмотрим сразу на пример использования:

Reciever reciever; DynamicQObject dynamic(&reciever, "signalCatched");  Tester tester; dynamic.addSlot(&tester, SIGNAL(signal1(int,int,QString)), "myslot1"); dynamic.addSlot(&tester, SIGNAL(signal2(QPoint)), "myslot2");  tester.emitSignal1(); tester.emitSingal2(); 

В конструктор DynamicQObject передаем указатель на любого наследника QObject и имя метода (Q_INVOKABLE), который будет вызывать при получении любого сигнала.
В классе Reciever у нас есть для имеется есть следующий метод: Q_INVOKABLE void signalCatched(const QString &signalName, const QVariantList &args), который просто выводить в консоль свои аргументы.
А в Tester есть два сигнала с указанными аргументами, и две функции генерирующие их, думаю всё должно быть понятно. Запускаем:

«myslot1» (QVariant(int, 123), QVariant(int, 456), QVariant(QString, «str») )
«myslot2» (QVariant(QPoint, QPoint(3,4) ) )

Видим, что всё работает:) Можно использовать абсолютно любые типы известные метасистеме Qt.

2. Слоты

Т.е. создание виртуальных сигналов и подключение их к обычным слотам, тут легко перепутать одно с другим… За это отвечают 3 функции: addSignal(), removeSignal() и activate().
Рассмотрим самое интересное. Создание сигнала:

bool DynamicQObject::addSignal(const QString &name, QObject *object, const char *slot) {     if (slot[0] != '1') {         qWarning() << "Use SLOT() macro";         return false;     }     int slotIdx = object->metaObject()->             indexOfSlot(&slot[1]); // without 1 added by SLOT() macro     if (slotIdx < 0) {         qWarning() << slot << "slot didn't exist";         return false;     } 

Как обычно, проверяем, что всё идёт как надо.

    QVector<int> parameterTypes;     QMetaMethod slotMethod = object->metaObject()->method(slotIdx);     for (int i = 0; i < slotMethod.parameterCount(); ++i)         parameterTypes.push_back(slotMethod.parameterType(i));      int signalIdx = -1;     for (int i = 0; i < m_slotList.count(); ++i) {         if (m_slotList[i].isEmpty == true) {             signalIdx = i;             break;         }     }     bool addEntry = false;     if (signalIdx == -1) {         addEntry = true;         signalIdx = m_signalList.count();     } 

Аналогично создаём вектор типов аргументов.

    if (!QMetaObject::connect(this, signalIdx + metaObject()->methodCount(),                               object, slotIdx)) {         qWarning() << "can't connect virtual signal" << name << "to slot" << slot;         return false;     }      if (addEntry) {         m_signalList.append({false, object, slotIdx, name, parameterTypes});     } else {         signal_t &signal = m_signalList[signalIdx];         signal.isEmpty = false;         signal.reciever = object;         signal.slotIdx = slotIdx;         signal.name = name;         signal.parameterTypes = parameterTypes;     }     m_signalHash.insert(name, signalIdx);      return true; } 

И снова всё почти также — подключаем наш виртуальный сигнал к слоту, а также сохраняем в m_signalHash пару имя — индекс сигнала. Таким образом при активации сигнала, мы получим его индекс из имени.
Удаление рассматривать не будем, а то и так очень много кода…

Активация сигнала

Тут уже есть новые моменты, а именно приведение типов.

bool DynamicQObject::activate(const QString &signalName, const QVariantList &args) {     int signalIdx = m_signalHash.value(signalName, -1);     if (signalIdx == -1) {         qWarning() << "signal" << signalName << "doesn't exist";         return false;     }      signal_t &signal = m_signalList[signalIdx]; 

Проверяем, что сигнал существует и извлекаем сохранённую ранее информацию.

    if (args.count() < signal.parameterTypes.count()) {         qWarning() << "parameters count mismatch:" << signalName                    << "provided:" << args.count()                    << "need >=:" << signal.parameterTypes.count();         return false;     } 

Проверяем, что аргументов достаточно, лишние просто отбросим…

    QVariantList argsCopy = args;      for (int i = 0; i < signal.parameterTypes.count(); ++i) {         if (!argsCopy[i].convert(signal.parameterTypes[i])) {             qWarning() << "can't cast parameter" << i << signalName;             return false;         }         m_parameters[i + 1] = argsCopy[i].data();     } 

Создаём копию списка аргументов, т.к. он константный, а нам нужно приводить типы, т.е. менять его. А далее пробуем привести каждый аргумент к нужному типу. Так, например, можно вызвать слот с аргументом типа int, передав activate аргумент типа QString, главное чтобы строка содержала число.
Пример:

    DynamicQObject dynamic(&reciever, "signalCatched");      Reciever reciever;     dynamic.addSignal("virtual_signal", &reciever, SLOT(slot1(int,QString)));      dynamic.activate("virtual_signal", QVariantList() << "123" << QString("qwerty") << 2 << 3); 

Соответственно Reciever содержит обычный слот. Мы создаём виртуальный сигнал и вызываем его со списком аргументов.
Вот что получается:

slot1_call 123 «qwerty»

Два лишних аргумента отбросились, а для первого было выполнено преобразование типов, не уверен, что это очень нужная вещь, но тут она получается сама по себе, хотя можно и запретить подобное поведение…

Пока всё. Далее разберёмся с методами, и прикрутим простенький сетевой интерфейс…

P.S. В написании данного класса, очень помогла статья: Dynamic Signals and Slots, из которой можно узнать много всего интересного.

ссылка на оригинал статьи http://habrahabr.ru/post/198780/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *