(With \"signal handler\" I mean slots, not handlers for POSIX signals.)
I need to \"connect\" (probably not using QObject::connect
directly)
I found a solution for my question, after looking into the code of Conan as suggested by HostileFork in the question's comments:
I wrote a customized qt_static_metacall
for a helper QObject by using a customized moc
output file (by moving the generated file into my sources and removing the class' header from my .pro file afterwards). I need to be careful, but it seems to be far less dirty than my suggested solution in the question.
For a class with some slots, here for example the two slots exampleA(int)
and exampleB(bool)
, it is defined like this:
void ClassName::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Q_ASSERT(staticMetaObject.cast(_o));
ClassName *_t = static_cast(_o);
switch (_id) {
case 0: _t->exampleA((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->exampleB((*reinterpret_cast< bool(*)>(_a[1]))); break;
default: ;
}
}
}
As you can see, it redirects the call to the "real" method on the object pointer provided by the callee.
I made a class with some slot without any arguments, which will be used as the target of the signal we want to inspect.
class GenericSignalMapper : public QObject
{
Q_OBJECT
public:
explicit GenericSignalMapper(QMetaMethod mappedMethod, QObject *parent = 0);
signals:
void mapped(QObject *sender, QMetaMethod signal, QVariantList arguments);
public slots:
void map();
private:
void internalSignalHandler(void **arguments);
QMetaMethod method;
};
The slot map()
never gets called in real, because we step in this calling process by putting our own method in the qt_static_metacall
(note that the meta method with ID 0 is another signal I explain in the next section, so the modified method is the case 1
):
void GenericSignalMapper::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Q_ASSERT(staticMetaObject.cast(_o));
GenericSignalMapper *_t = static_cast(_o);
switch (_id) {
case 0: _t->mapped((*reinterpret_cast< QObject*(*)>(_a[1])),(*reinterpret_cast< QMetaMethod(*)>(_a[2])),(*reinterpret_cast< QVariantList(*)>(_a[3]))); break;
case 1: _t->internalSignalHandler(_a); break;
default: ;
}
}
}
What we do is: We just pass the uninterpreted argument array to our own handler, because we can't be specific about its types (or even the count). I defined this handler as follows:
void GenericSignalMapper::internalSignalHandler(void **_a)
{
QVariantList args;
int i = 0;
foreach(QByteArray typeName, method.parameterTypes())
{
int type = QMetaType::type(typeName.constData());
QVariant arg(type, _a[++i]); // preincrement: start with 1
// (_a[0] is return value)
args << arg;
}
emit mapped(sender(), method, args);
}
Finally, some other class may connect to the mapped
signal, which will provide the sender object, the signal as a QMetaMethod (from which we can read the name) and the arguments as QVariants.
This is not a full solution, but the final step is easy: For each signal of the class to be inspected, we create a GenericSignalMapper providing the meta method of the signal. We connect map to the object and mapped to the final receiver, which is then able to handle (and distinguish) all emitted signals by the source object.
I still have problems converting the Fixed. void*
arguments to QVariants._a
also includes a placeholder for the return value at index 0
, so arguments start at index 1
.
In this example, the "final step" (create and connect mappers for each signal) is done manually.
The class to be inspected:
class Test : public QObject
{
Q_OBJECT
public:
explicit Test(QObject *parent = 0);
void emitTestSignal() {
emit test(1, 'x');
}
signals:
void test(int, char);
};
The final handler class receiving all signals via the mappers:
class CommonHandler : public QObject
{
Q_OBJECT
public:
explicit CommonHandler(QObject *parent = 0);
signals:
public slots:
void handleSignal(QObject *sender, QMetaMethod signal, QVariantList arguments)
{
qDebug() << "Signal emitted:";
qDebug() << " sender:" << sender;
qDebug() << " signal:" << signal.signature();
qDebug() << " arguments:" << arguments;
}
};
The code where we create the objects and connect them:
CommonHandler handler;
// In my scenario, it is easy to get the meta objects since I loop over them.
// Here, 4 is the index of SIGNAL(test(int,char))
QMetaMethod signal = Test::staticMetaObject.method(4);
Test test1;
test1.setObjectName("test1");
Test test2;
test2.setObjectName("test2");
GenericSignalMapper mapper1(signal);
QObject::connect(&test1, SIGNAL(test(int,char)), &mapper1, SLOT(map()));
QObject::connect(&mapper1, SIGNAL(mapped(QObject*,QMetaMethod,QVariantList)), &handler, SLOT(handleSignal(QObject*,QMetaMethod,QVariantList)));
GenericSignalMapper mapper2(signal);
QObject::connect(&test2, SIGNAL(test(int,char)), &mapper2, SLOT(map()));
QObject::connect(&mapper2, SIGNAL(mapped(QObject*,QMetaMethod,QVariantList)), &handler, SLOT(handleSignal(QObject*,QMetaMethod,QVariantList)));
test1.emitTestSignal();
test2.emitTestSignal();
Output:
Signal emitted:
sender: Test(0xbf955d70, name = "test1")
signal: test(int,char)
arguments: (QVariant(int, 1) , QVariant(char, ) )
Signal emitted:
sender: Test(0xbf955d68, name = "test2")
signal: test(int,char)
arguments: (QVariant(int, 1) , QVariant(char, ) )
(The char
argument doesn't get printed correctly, but it is stored in the QVariant correctly. Other types work like a charm.)