(With "signal handler" I mean slots, not handlers for POSIX signals.)
I need to "connect" (probably not using QObject::connect
directly) all signals from an instance of a (not yet known) subclass of QObject to one single slot of another QObject. I need this in order to send the signal (with arguments) over network (for an own RPC system with support for signals).
(With "not yet known" I mean that my code should be as generic as possible. So it souldn't contain a connect
statement for each signal in each class I'm using with my RPC system, but provide something like RPC::connectAllSignals(QObject*);
, which then scanns all signals during runtime and connects them.)
What I'd like to achieve is: Handle all signals and serialise them (signal name + arguments). I already can serialise the arguments, but I don't know how to get the signal name. After googling, it seems to be impossible to use something similar like there is sender()
for the QObject instance. So I need to do something far more complicated.
My current type system for passing the arguments to a target function on the remote end is restricted to some types anyway. (That's because I need qt_metacall
, which excepts the arguments to be of type void*
with the "correct types" behind them. My RPC system uses QVariants with only a couple of types internally and I convert them to void*
of the correct types using custom methods. I heard about QVariant::constData
too late to use it, and it probably won't fit anyway; so I will stick to my type conversion if there is no drawback.)
The target slot, where all signals should be mapped to, should look similar to this:
void handleSignal(QByteArray signalName, QVariantList arguments);
It would be best if the solution is supported by C++03, so I only want to use variadic templates if it is a big drawback to not use them. In this case C++11 is OK, so I'm also happy about answers using C++11.
Now my possible solution to the question I'm thinking about:
I could scan all signals of the object using its QMetaObject
and then creating a QSignalMapper
(or something similar which passes all arguments) for each signal. This is easy, and I need no help on this part. As mentioned before, I'm already restricted to some types for arguments, and I can also live with a restriction on the argument count.
It sounds like a dirty hack, but I could use some sort of custom, template-based signal mappers like this (in this example for three arguments):
template<class T1, class T2, class T3>
class MySignalMapper : public QObject
{
Q_OBJECT
public:
void setSignalName(QByteArray signalName)
{
this->signalName = signalName;
}
signals:
void mapped(QByteArray signalName, QVariantList arguments);
public slots:
void map(T1 arg1, T2 arg2, T3 arg3)
{
QVariantList args;
// QVariant myTypeConverter<T>(T) already implemented:
args << myTypeConverter(arg1);
args << myTypeConverter(arg2);
args << myTypeConverter(arg3);
emit mapped(signalName, args);
}
private:
QByteArray signalName;
};
Then I could connect a QMetaMethod called method
(which is known to be a signal) of a QObject called obj
like this (which might be generated using some sort of script for all supported types and argument counts... yeah... it's getting dirty!):
// ...
}
else if(type1 == "int" && type2 == "char" && type3 == "bool")
{
MySignalMapper<int,char,bool> *sm = new MySignalMapper<int,char,bool>(this);
QByteArray signalName = method.signature();
signalName = signalName.left(signalName.indexOf('(')); // remove parameters
sm->setMember(signalName);
// prepend "2", like Qt's SIGNAL() macro does:
QByteArray signalName = QByteArray("2") + method.signature();
// connect the mapper:
connect(obj, signalName.constData(),
sm, SLOT(map(int,char,bool)));
connect(sm, SIGNAL(mapped(int,char,bool)),
this, SLOT(handleSignal(const char*,QVariantList)));
}
else if(type1 == ...)
{
// ...
As this may work, it really is a dirty solution. I'd need either a lot of macros to cover all combinations of types for at most N
arguments (where N
is about 3 to 5, not yet known), or a simple script generating the code for all cases. The problem is that this will be a lot of cases, as I'm supporting about 70 different types per argument (10 primitive types + nested lists and maps with depth 2 for every type of them). So for an argument count limit of N
there are N
^ 70 cases to cover!
Is there a completely different approach for this objective, which I'm overlooking?
UPDATE:
I solved the problem on my own (see answer). If you are interested in the full source code, see my repository on bitbucket of my RPC system, which I have just published: bitbucket.org/leemes/qtsimplerpc
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 customizedmoc
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)
andexampleB(bool)
, it is defined like this: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.
The slot
map()
never gets called in real, because we step in this calling process by putting our own method in theqt_static_metacall
(note that the meta method with ID 0 is another signal I explain in the next section, so the modified method is thecase 1
):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:
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 theFixed.void*
arguments to QVariants._a
also includes a placeholder for the return value at index0
, so arguments start at index1
.Example:
In this example, the "final step" (create and connect mappers for each signal) is done manually.
The class to be inspected:
The final handler class receiving all signals via the mappers:
The code where we create the objects and connect them:
Output:
(The
char
argument doesn't get printed correctly, but it is stored in the QVariant correctly. Other types work like a charm.)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 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 overridingqt_metacall
, for example: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 asstatic 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 listI 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.