How signal and slots are implemented under the hood?

不羁岁月 提交于 2019-12-28 05:49:46

问题


This question is already asked in this forum but I don't understand the concept.

I was reading around and it seems that signal and slots are implemented using function pointers i.e the signal is one big function which inside it calls all connected slots (function pointers). Is this correct? And what is the role of the generated moc files in the whole story? I don't understand how the signal function knows which slots to call i.e which slots are connected to this signal.

Thanks for your time


回答1:


Qt implements these things in a way that resembles interpreted languages. I.e. it constructs symbol tables that map signal names to function pointers, maintains them and looks up the function pointer by function name where needed.

Each time you emit a signal, i.e. write

emit something();

you actually call the something() function, which it automatically generated by meta object compiler and placed into a *.moc file. Within this function it's checked what slots this signal is connected to at the moment, and appropriate slot functions (which you implemented in your own sources) are sequentially called via the symbol tables (in the way described above). And emit, like other Qt-specific keywords, are just discarded by C++ preprocessor after *.moc were generated. Indeed, in one of the Qt headers (qobjectdefs.h), there exist such lines:

#define slots 
#define signals protected
#define emit

Connection function (connect) just modifies the symbol tables maintained within *.moc files, and the arguments passed to it (with SIGNAL() and `SLOT macros) are also preprocessed to match the tables.

That's the general idea. In his or her another answer, ジョージ supplies us with links to trolltech mailing list and to another SO question on this topic.




回答2:


I think I should add the following.

There is another linked question -- and there is a very good article that can be considered as a quite detailed expansion to it's answer; here is this article again, with improved (though still not perfect) code syntax highlighting.

Here's my short retelling of it, that may be prone to mistakes )

Basically when we insert the Q_OBJECT macro in our class definition, the preprocessor expands it to a static QMetaObject instance declaration, one that would be shared by all instances of the same class:

class ClassName : public QObject // our class definition
{
    static const QMetaObject staticMetaObject; // <--= Q_OBJECT results to this

    // ... signal and slots definitions, other stuff ...

}

This instance, in turn, on initialization will store the signatures ("methodname(argtype1,argtype2)") of the signals and the slots, what will allow to implement the indexOfMethod() call, which returns, well, method's index by it's signature string :

struct Q_CORE_EXPORT QMetaObject
{    
    // ... skip ...
    int indexOfMethod(const char *method) const;
    // ... skip ...
    static void activate(QObject *sender, int signal_index, void **argv);
    // ... skip ...
    struct { // private data
        const QMetaObject *superdata; // links to the parent class, I guess
        const char *stringdata; // basically, "string1\0string2\0..." that contains signatures and other names 
        const uint *data; // the indices for the strings in stringdata and other stuff (e.g. flags)
        // skip
    } d;
};

Now when the moc creates the moc_headername.cpp file for the Qt class header headername.h, it puts there the signature strings and other data that are necessary for correct initialization of the d structure, and then writes the initialization code for the staticMetaObject singleton using this data.

Another important thing it does is generation of the code for the object's qt_metacall() method, that takes an object's method id and an array of argument pointers and calls the method via a long switch like this:

int ClassName::qt_metacall(..., int _id, void **_args)
{
    // ... skip ...
    switch (_id) {
        case 0: signalOrSlotMethod1(_args[1], _args[2]); break; // for a method with two args
        case 1: signalOrSlotMethod2(_args[1]); break; // for a method with a single argument
        // ... etc ...
    }
    // ... skip ...
}

Last, for every signal moc generates an implementation, which contains a QMetaObject::activate() call :

void ClassName::signalName(argtype1 arg1, argtype2 arg2, /* ... */)
{
    void *_args[] = { 0, // this entry stands for the return value
                      &arg1, // actually, there's a (void*) type conversion
                      &arg2, // in the C++ style
                      // ...
                    };
    QMetaObject::activate( this, 
                           &staticMetaObject, 
                           0, /* this is the signal index in the qt_metacall() map, I suppose */ 
                           _args
                         );
}

Finally, the connect() call translates the string method signatures to their integer ids (the ones used by qt_metacall()) and maintains a list of signal-to-slot connections; when the signal is emitted, the activate() code goes through this list and calls the appropriate object "slots" via their qt_metacall() method.

To sum up, the static QMetaObject instance stores the "meta-information" (method signature strings etc), a generated qt_metacall() method provides "a method table" that allows any signal/slot to be called by an index, signal implementations generated by moc use these indexes via activate(), and finally connect() does the job of maintaining a list of signal-to-slot index maps.

*Note: there's a complication of this scheme used for the case when we want to deliver signals between different threads ( I suspect that one has to look at the blocking_activate() code ), but I hope the general idea remains the same )

This is my very rough understanding of the linked article, which easily may be wrong, so I do recommend to go and read it directly )

PS. As I'd like to improve my understanding of Qt implementation -- please let me know of any inconsistencies in my retelling !


Since my other (earlier) answer was deleted by some zealous editor, I will append the text here ( I am missing few details that were not incorporated in Pavel Shved's post, and I doubt the person who deleted the answer cared. )

@Pavel Shved:

I am pretty sure that somewhere in Qt headers there exists a line:

#define emit

Just to confirm: found it in old Qt code by Google Code Search. It is quite likely that it is still there ) ; the found location path was :

ftp://ftp.slackware-brasil.com.br› slackware-7.1› contrib› kde-1.90› qt-2.1.1.tgz› usr› lib› qt-2.1.1› src› kernel› qobjectdefs.h


Another complementory link: http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html -- see the answer by Andreas Pakulat


And here is another piece of the answer : Qt question: How do signals and slots work?



来源:https://stackoverflow.com/questions/1406940/how-signal-and-slots-are-implemented-under-the-hood

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!