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

Part 1 - Properties We
continue to deal with the Qt metasystem. This time, consider creating virtual signals and slots.
1. Signals
More precisely, connection to any signals. Everything here is very similar to working with properties, only arguments can be from 0 to 10. The goal is to get a DynamicQObject class that can connect to any signal and, when activated, call some method with the signal name and the arguments passed in the form of 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 parameterTypes;
} slot_t;
QVector m_slotList;
// virtual signals
typedef struct {
bool isEmpty; // // true after removeSignal()
QObject *reciever;
int slotIdx;
QString name;
QVector parameterTypes;
} signal_t;
QVector m_signalList;
QHash 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 signal of the object object. Let's see how everything works here:
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;
}
We check if 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 SIGNAL () macro.
QByteArray theSignal = QMetaObject::normalizedSignature(&signal[1]);
int signalId = object->metaObject()->indexOfSignal(theSignal);
if (signalId < 0) {
qWarning() << "signal" << signal << "doesn't exist";
return false;
}
QVector parameterTypes;
QMetaMethod signalMethod = object->metaObject()->method(signalId);
for (int i = 0; i < signalMethod.parameterCount(); ++i)
parameterTypes.push_back(signalMethod.parameterType(i));
As well as the last time, we get the signal index, and then we get QMetaMethod on this index. From which we get the signal arguments one at a time, create a vector from them. Further we will reduce the obtained arguments to precisely 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 very simple here, we find an empty record (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 ()), because our indices are tied to slots, their shift would lead to the call of invalid slots. It would be possible to use QHash or QMap here, but I thought that they remove slots much less often than they create, but they call it very often, so the vector is clearly more efficient, because its index access is performed for O (1), and for QMap and QHash in the worst case, for 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 save all the necessary information about him.
Slot call
Everything here 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 actually called, and also 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 the previously saved information about the slot. And then we create a list of QVariants from the received arguments and 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 a usage example right away:
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 QObject descendant and the method name (Q_INVOKABLE), which will be called when any signal is received.
In the Reciever class we have for there is the following method: Q_INVOKABLE void signalCatched (const QString & signalName, const QVariantList & args) , which simply displays its arguments to the console.
And in Tester there are two signals with the specified arguments, and two functions that generate them, I think everything should be clear. We launch:
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 types known to the Qt metasystem.
2. Slots
Those. creating virtual signals and connecting them to ordinary slots, it’s easy to confuse one with the other ... 3 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 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 pair of name - signal index . Thus, when the signal is activated, we get its index from the name.
We will not consider deleting, or even a lot of code ...
Signal activation
There are already new points, namely type 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 verify that the signal exists and retrieve the previously stored 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, we just drop the superfluous ...
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, as it is constant, but we need to cast types, i.e. change it. And then we try to cast each argument to the desired type. So, for example, you can call a slot with an argument of type int, passing the activate argument of 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 a list of arguments.
Here is the result:
slot1_call 123 "qwerty"
Two extra arguments were discarded, and for the first, type conversion was performed, I'm not sure that this is a very necessary thing, but here it turns out on its own, although you can forbid this behavior ... That's
all. Next, we will figure out the methods and fasten a simple network interface ...
PS In writing this class, the article: Dynamic Signals and Slots helped a lot, from which you can learn a lot of interesting things.