2009-09-10 14 views
22

Cette question est déjà posée dans ce forum mais je ne comprends pas le concept. Je lisais autour et il semble que le signal et les emplacements sont implémentés en utilisant des pointeurs de fonction, c'est-à-dire que le signal est une grande fonction qui à l'intérieur appelle tous les emplacements connectés (pointeurs de fonction). Est-ce correct? Et quel est le rôle des fichiers moc générés dans toute l'histoire? Je ne comprends pas comment la fonction de signal sait quels emplacements appeler, c'est-à-dire quels emplacements sont connectés à ce signal.Comment le signal et les slots sont-ils implémentés sous le capot?

Merci pour votre temps

+0

question Nice. Voir aussi: http://stackoverflow.com/questions/1413777/how-boost-implements-signals-and-slots – elcuco

Répondre

15

Qt met en œuvre ces choses d'une manière qui ressemble à des langages interprétés. C'est à dire. il construit des tables de symboles qui mappent les noms de signaux aux pointeurs de fonction, les maintient et recherche le pointeur de fonction par nom de fonction si nécessaire.

Chaque fois que vous émettez un signal, à savoir écrire

emit something(); 

vous fait appeler la fonction something(), qui a généré automatiquement par le compilateur méta objet et placé dans un fichier *.moc. Dans cette fonction, on vérifie à quels emplacements ce signal est actuellement connecté, et les fonctions de slots appropriées (que vous avez implémentées dans vos propres sources) sont appelées séquentiellement via les tables de symboles (de la manière décrite ci-dessus). Et emit, comme d'autres mots-clés spécifiques à Qt, sont simplement rejetés par le préprocesseur C++ après *.moc ont été générés. En effet, dans l'un des en-têtes Qt (qobjectdefs.h), il existe de telles lignes:

#define slots 
#define signals protected 
#define emit 

fonction de connexion (connect) modifie uniquement les tables de symboles entretenus dans les *.moc fichiers, et les arguments qui lui sont transmis (avec SIGNAL() et ` Les macros SLOT) sont également prétraitées pour correspondre aux tables.

C'est l'idée générale. Dans son autre réponse, ジョージ nous fournit des liens to trolltech mailing list et another SO question sur ce sujet.

+4

Fondamentalement correct, vous pouvez définir un point d'arrêt à l'émission puis passer par le processus de signalisation. En général, les signaux peuvent être placés en file d'attente, mais cela est nécessaire lorsque vous souhaitez connecter deux objets dans des threads différents. –

5

Je pense que je devrais ajouter ce qui suit.

Il y a another linked question - et il y a a very good article qui peut être considéré comme une expansion assez détaillée à answer; here is this article again, avec amélioration de la syntaxe du code (mais pas toujours parfaite).

Voici mon court récit de celui-ci, qui peuvent être sujettes à des erreurs)

Fondamentalement quand nous insérons la Q_OBJECT macro dans notre définition de la classe, le préprocesseur se dilate à une déclaration QMetaObject d'instance statique, qui serait partagé par toutes les instances de la même classe:

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

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

} 

Cette instance, à son tour, lors de l'initialisation stocke les signatures ("methodname(argtype1,argtype2)") des signaux et les machines à sous, ce qui permettra de mettre en œuvre le indexOfMethod() appel, ce qui revient, eh bien, l'indice de la méthode par sa chaîne de signature:

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; 
}; 

Maintenant, lorsque le moc crée le fichier moc_headername.cpp pour l'en-tête de classe Qt headername.h, il y met les chaînes de signature et d'autres données qui sont nécessaires pour initialisation correcte de la structure d, puis écrit le code d'initialisation pour le singleton staticMetaObject en utilisant ces données.

Une autre chose importante qu'il fait est la génération du code pour la méthode qt_metacall() de l'objet, qui prend l'ID de méthode d'un objet et un tableau de pointeurs d'argument et appelle la méthode par une longue switch comme ceci:

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 ... 
} 

Enfin, pour tous les signaux moc génère une mise en œuvre, qui contient un appel QMetaObject::activate():

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 
         ); 
} 

Enfin, l'appel connect() traduit le STRI ng les signatures de méthode à leurs identifiants entiers (ceux utilisés par qt_metacall()) et gère une liste de connexions signal-emplacement; lorsque le signal est émis, le code activate() passe par cette liste et appelle l'objet approprié "slots" via sa méthode qt_metacall(). Pour résumer, l'instance statique QMetaObject stocke les "méta-informations" (chaînes de signature de méthode, etc.), une méthode générée qt_metacall() fournit "une table de méthodes" qui permet à n'importe quel signal/emplacement d'être appelé par un index, signal implémentations générées par moc utilisent ces index via activate(), et finalement connect() fait le travail de maintenir une liste de cartes d'index de signal-à-logement. * Note: il y a une complication de ce schéma utilisé dans le cas où l'on veut délivrer des signaux entre différents threads (je suppose qu'on doit regarder le code blocking_activate()), mais j'espère que l'idée générale reste la même)

Ceci est ma compréhension très approximative de l'article lié, ce qui peut facilement se tromper, donc je ne recommande d'aller lire directement)

PS. Comme je voudrais améliorer ma compréhension de la mise en œuvre de Qt - s'il vous plaît laissez-moi savoir des incohérences dans mon récit!


Depuis mon autre (plus tôt) réponse a été supprimée par un éditeur zélé, je vais ajouter le texte ici (je manque quelques détails qui étaient pas incorporés dans le poste de Pavel Shved, et je doute la personne qui supprimé la réponse se souciait.)

@Pavel Shved:

Je suis assez sûr que quelque part en tête de Qt existe-t-il une ligne:

#define emit

Juste pour confirmer: trouvé dans l'ancien code Qt par Google Code Search. Il est fort probable qu'il soit toujours là); le chemin de l'emplacement trouvé était:

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> noyau> qobjectdefs.h


Un autre lien complementory: http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html - voir la réponse par Andreas Pakulat


Et voici un autre morceau de la réponse: Qt question: How do signals and slots work?