Pourquoi la méthode GObject est-elle toujours appelée même si les arguments de rappel ne correspondent pas à ceux de XML?

Supposons que j’ai une méthode comme celle-ci

     

Dans mon tableau de méthodes j’ai:

 { (GCallback) child_test_set_age, dbus_glib_marshal_child_test_BOOLEAN__UINT_POINTER, 0 } 

et la signature de méthode GObject droite est:

 gboolean child_test_set_age (ChildTest *childTest, guint ageIn, GError** error) 

Pourquoi ma méthode, child_test_set_age() , est-elle toujours appelée sur DBus même si les arguments de rappel ne correspondent pas à ceux spécifiés dans mon XML? Par exemple, si j’ajoute un autre argument après guint ageIn , comme un caractère char* ou une guint ou un autre type aléatoire?

J’ai remarqué que cela ne fonctionnerait pas si la fonction DBus incluait des membres avec une direction OUT. Il semble que tout argument de type IN non nécessaire soit rejeté et que l’appel soit effectué comme d’habitude.

Bien que je pense que cela ne fait aucune différence, j’utilise D-BUS Binding Tool 0.94, glib-2.30.0 et dbus-glib 0.94.

Vous avez découvert un détail intéressant en raison du langage C. Les fonctions en C sont fortement typées et vous devriez donc avoir des fonctions permettant de traiter tous les types de rappel possibles, comme le cauchemar suivant:

 g_signal_connect_callback_void__void(GObject *object, gchar *signal, void (*callback)(GObject *, gpointer), gpointer data); g_signal_connect_callback_void__guint(GObject *object, gchar *signal, void (*callback)(GObject *, guint, gpointer), gpointer data); g_signal_connect_callback_gboolean__gdkevent(GObject *object, gchar *signal, gboolean (*callback)(GObject *, GdkEvent *, gpointer), gpointer data); 

Heureusement, deux fonctionnalités du langage C permettent d’éviter ce désordre.

  • Les pointeurs de fonction ont la même taille, quels que soient le type et les arguments de la fonction.
  • La convention d’appel C (techniquement une implémentation dépendante du compilateur et de l’architecture!)

Étant donné que les pointeurs de fonction ont tous la même taille, vous GCallback les GCallback en void (*callback)(void) , ce pour quoi GCallback est un typedef. GCallback est utilisé dans toutes les API de la plate-forme GLib pour les rappels pouvant comporter des nombres variables et des types d’arguments. C’est pourquoi vous devez child_test_set_age GCallback en GCallback dans votre exemple de code ci-dessus.

Mais même si vous pouvez faire passer les pointeurs de fonction comme s’ils étaient tous identiques, comment vous assurez-vous que les fonctions obtiennent réellement leurs arguments? C’est ce que la convention d’appel C est pour. Le compilateur génère un code tel que l’appelant pousse les arguments de la fonction sur la stack, la fonction lit les arguments de la stack mais ne les extrait pas et, à son retour, l’appelant les retire de la stack. Ainsi, l’appelant peut pousser un nombre d’arguments différent de celui attendu par la fonction, à condition que la fonction puisse trouver tous les arguments auxquels elle tente d’accéder.

Illustrons cela avec votre exemple: appel de la méthode child_test_set_age(ChildTest *childTest, guint ageIn, GError **error) . Supposons que les pointeurs et les entiers ont la même taille sur votre plate-forme, et je vais sauter quelques détails afin de faire passer l’idée générale.

L’appelant place les arguments sur la stack:

 +------------+ | &childTest | arg1 +------------+ | 25 | arg2 +------------+ | NULL | arg3 +------------+ 

… et appelle la fonction. La fonction récupère cette stack et y cherche ses arguments:

 +------------+ | &childTest | ChildTest *childTest +------------+ | 25 | guint ageIn +------------+ | NULL | GError **error +------------+ 

Tout va bien. Ensuite, la fonction retourne et l’appelant extrait les arguments de la stack.

Maintenant, cependant, si vous atsortingbuez à votre fonction un type différent de celui de la signature DBus dans le XML, supposons que child_test_set_age(ChildTest *childTest, guint ageIn, guint otherNumberIn, GError **error) , appliquons les mêmes arguments à la stack. , mais votre fonction les interprète différemment:

 +------------+ | &childTest | ChildTest *childTest ...OK so far +------------+ | 25 | guint ageIn ...still OK +------------+ | NULL | guint otherNumberIn ...will be 0 if you try to read it, but OK +------------+ | undefined | GError **error ...will be garbage! | behavior | | land!! | | ... | 

Les deux premiers parameters sont bien. Le troisième, étant donné que DBus ne sait pas que vous vous attendez à une autre guint , va être le GError ** en guint . Si vous êtes assez chanceux que ce pointeur soit à NULL , alors otherNumberIn sera égal à 0, mais sinon ce sera un emplacement de mémoire converti en un entier: garbage.

Le quasortingème paramètre est particulièrement dangereux, car il essaiera de lire quelque chose en dehors de la stack, et vous n’avez aucune idée de ce qu’il contient. Donc, si vous essayez d’accéder au pointeur d’ error , vous allez probablement faire planter votre programme avec une erreur de segmentation.

Toutefois, si vous parvenez à exécuter la fonction sans erreur de segmentation, l’appelant extraira proprement trois arguments de la stack après le retour de la fonction et tout reviendra à la normale. C’est pourquoi votre programme semble toujours fonctionner.

Les parameters “out” sont implémentés à l’aide d’arguments de pointeur, c’est pourquoi ils ne fonctionnent pas; il est presque garanti que la fonction écrit sur une adresse mémoire non valide car les pointeurs sont parasites.

En résumé, votre fonction peut avoir une signature différente si les conditions suivantes sont remplies:

  • votre compilateur utilise la convention d’appel C
  • votre fonction a le même nombre d’arguments (ou moins) que celui attendu par l’appelant
  • vos arguments ont chacun la même taille que les arguments que l’appelant s’attend à pousser
  • vos types d’arguments ont un sens lors de la formulation des arguments que l’appelant s’attend à pousser