(With \"signal handler\" I mean slots, not handlers for POSIX signals.)
I need to \"connect\" (probably not using QObject::connect
directly)
I was looking for a generic signal handler for the same reason, i.e. forwarding signal calls via RPC. There is a very interesting and detailed description of the QObject-QMetaObject magic in a QtDevDays presentation. In particular, they also describe the desire to inspect generic signals for debugging or interfacing with scripting languages - so this is a perfect read.
Long story short: Your solution was to modify qt_static_metacall
in the moc code. (Now in Qt5?) The same thing can be achieved by subclassing your QObject based class and overriding qt_metacall
, for example:
class QRpcService : public QRpcServiceBase
{
public:
explicit QRpcService(QTcpServer* server, QObject *parent = 0);
virtual ~QRpcService();
virtual int qt_metacall(QMetaObject::Call, int, void**);
private:
static int s_id_handleRegisteredObjectSignal;
};
The magic capture-all-slot is just a dummy method defined in the base class (here void handleRegisteredObjectSignal()
) that takes nothing and does nothing. I query its meta-method-id in the constructor and store it as static int
to avoid searching for it every time.
Within this custom metacall handler you intercept the calls to your magic-capture-all slot and inspect the sender object and signal. This provides all the type information required to convert the void**
arguments to a QVariant list
int QRpcService::qt_metacall(QMetaObject::Call c, int id, void **a)
{
// only handle calls to handleRegisteredObjectSignal
// let parent qt_metacall do the rest
if (id != QRpcService::s_id_handleRegisteredObjectSignal)
return QRpcServiceBase::qt_metacall(c, id, a);
// inspect sender and signal
QObject* o = sender();
QMetaMethod signal = o->metaObject()->method(senderSignalIndex());
QString signal_name(signal.name());
// convert signal args to QVariantList
QVariantList args;
for (int i = 0; i < signal.parameterCount(); ++i)
args << QVariant(signal.parameterType(i), a[i+1]);
// ...
// do whatever you want with the signal name and arguments
// (inspect, send via RPC, push to scripting environment, etc.)
// ...
return -1;
}
I just handled everything within this method, but you could also re-emit all the information that was gathered in another signal and attach to that at runtime.
If anyone is interested, I also set up a repository with my solution here.
You can make a generic dispatch per argument, and about the SLOT/SIGNAL they are just strings so it's not problem to forge them. It's all about making one template function that will pass per argument into the the dispatch and merge all the results. This can even have unlimited number of arguments if you use c++11.
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<ClassName *>(_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<GenericSignalMapper *>(_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.)