📜 ⬆️ ⬇️

Qt Meta System over Network. Part 2 - Signals and Slots


Part 1 - Properties
We continue to deal with the Qt metasystem. This time we will consider the creation of virtual signals and slots.

1. Signals


Or rather connect to any signals. Everything is very similar to working with properties, only arguments can be from 0 to 10. The goal is to get the DynamicQObject class, which can connect to any signal and when activated, call some method with the name of the signal and the passed arguments as QVariantList. I will also break the code into pieces with comments, let's proceed:
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 - creates a new virtual slot named slotName and connects it to the signal object of the object. Let's see how it all works:
 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; } 

Check whether there is already a slot with the same name, as well as the presence of the character '2' at the beginning of its name, this symbol adds the macro 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)); 

Just like last time, we get the signal index, and then we get the QMetaMethod by this index. From which we get the signal arguments one by one, create a vector from them. Further we will bring the received arguments to these types.
  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(); } 

Everything is quite simple here, we find an empty entry (or create a new one) in m_slotList, where we will save information about the created slot. Empty records are formed after removing slots (removeSlot ()), since Our indexes are tied to slots, their shift would lead to the call of invalid slots. One could apply QHash or QMap here, but I thought that slots are removed much less frequently than they are created, and called very often, so the vector is clearly more efficient, because its index access is performed for O (1), while for QMap and QHash, in the worst case, it is O (logn) and O (n), respectively.
Actually it remains only to connect to the signal:
  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; } 

And keep all the necessary information about him.

Slot call

Here everything looks like a focus with properties, we create our own implementation of qt_metacall, only now we can have more arguments:
 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()); 

We check that the slot (metamethod) is called, and that the index is correct.
  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)); } 

We get previously saved information about the slot. And further we create a list of QVariants from the resulting arguments and the stored types.
  QMetaObject::invokeMethod(m_mapTo, m_catchMethod, Q_ARG(QString, slotInfo.name), Q_ARG(QVariantList, parameters)); return -1; } 

It remains only to call the method specified in the constructor with the name of the slot, and arguments.

There is nothing interesting in removeSlot (), so let's take a look at the usage example:
 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(); 

In the DynamicQObject constructor, we pass a pointer to any successor to the QObject and the name of the method (Q_INVOKABLE) that will be called upon receipt of any signal.
In the Reciever class we have for there is the following method: Q_INVOKABLE void signalCatched (const QString & signalName, const QVariantList & args) , which simply output its arguments to the console.
And in Tester there are two signals with the specified arguments, and two functions generating them, I think everything should be clear. Run:
"Myslot1" (QVariant (int, 123), QVariant (int, 456), QVariant (QString, "str"))
Myslot2 (QVariant (QPoint, QPoint (3,4)))

We see that everything works :) You can use absolutely any type of known Qt metasystem.
')

2. Slots


Those. creating virtual signals and connecting them to regular slots, it's easy to confuse one with another ... Three functions are responsible for this: addSignal (), removeSignal () and activate ().
Consider the most interesting. Signal creation:
 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; } 

As usual, we check that everything is going as it should.
  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(); } 

Similarly, we create a vector of argument types.
  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; } 

And again everything is almost the same - we connect our virtual signal to the slot, and also save in m_signalHash a name pair - the signal index . Thus, when the signal is activated, we get its index from the name.
Deletion will not be considered, and even so a lot of code ...

Signal activation

There are already new moments, namely the casting.
 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]; 

We check that the signal exists and retrieve the previously saved information.
  if (args.count() < signal.parameterTypes.count()) { qWarning() << "parameters count mismatch:" << signalName << "provided:" << args.count() << "need >=:" << signal.parameterTypes.count(); return false; } 

We check that there are enough arguments, just drop the extra ones ...
  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(); } 

Create a copy of the argument list, since it is constant, and we need to type, i.e. change it. And then try to bring each argument to the desired type. For example, you can call a slot with an argument of type int, passing the argument to type QString, the main thing is that the string contains a number.
Example:
  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); 

Accordingly, Reciever contains a regular slot. We create a virtual signal and call it with an argument list.
This is what happens:
slot1_call 123 "qwerty"

Two extra arguments were discarded, and for the first one, a type conversion was performed, I'm not sure that this is a very necessary thing, but then it turns out by itself, although it is possible to prohibit such behavior ...

For now. Next, we will deal with the methods, and fasten a simple network interface ...

PS In writing this class, it was very helpful article: Dynamic Signals and Slots , from which you can learn a lot of interesting things.

Source: https://habr.com/ru/post/198780/


All Articles