Dans le cadre de l'évolution en cours du logiciel wxMidiPiano (un piano virtuel pour clavier USB), il était question de faire un callback vers une méthode de classe. La particularité est que l'appel implique 2 pointeurs : celui de l'instance et celui de la méthode de classe. Cet article explique en français ce qu'il faut retenir pour vous aider à faire communiquer vos logiciels.
Pour les plus pressés, voici le résultat final commenté plus bas :
// Structure of the callback typedef int (__stdcall *ty_cbfct)(void*); // Class with a private value returned by the callback class Life { private: int value = 42; public: static int __stdcall meaning(void* instance) { return ((Life*)instance)->value; } }; bool unit_test() { // Convert the object into 2 pointers Life cls; void* obj = &cls; void* cb = &Life::meaning; // Retrieve the value by a dynamic function call ty_cbfct fct = (ty_cbfct)(cb); return 42 == (*fct)(obj); } bool ok = unit_test();
Commençons par définir le format du callback. Il s'agit d'un typedef de fonction avec le format des paramètres. Il faut une définition pour chaque différent callback.
Le callback doit être une méthode publique d'une classe. Il est également statique, car il faut pouvoir l'appeler par un nom sans équivoque. Le 2ème pointeur, c'est-à-dire l'instance this, est fourni en paramètre de l'appel.
Le choix d'un standard call (__stdcall) ou d'un C declaration (__cdecl) dépend de la convention d'appel voulue. La C declaration est la plus traditionnelle, mais les API de Windows sont en standard call. Dans le cas présent, l'emploi d'un standard call est tout à fait arbitraire.
La classe Life de l'exemple a pour seul objectif de renvoyer la valeur 42 quand on appelle son unique méthode publique.
Dans la procédure de test unitaire, on commence par instancier la classe dans la variable "cls", puis on définit 2 pointeurs génériques de type void*. Pour l'illustration du mécanisme, le callback va être appelé au sein du même programme. Malgré le type void*, il faudra toujours respecter les paramètres réels de la fonction appelée.
La variable "fct" est dirigée vers le callback statique via un cast statique (réinterprété est possible aussi) noté ici entre parenthèses. Ensuite, on appelle la fonction en indiquant l'instance cible en paramètres, car c'est de la responsabilité du programme appelant de redonner cette info.
Par un artifice de programmation trivial, la fonction statique est capable de réinterpréter le paramètre d'instance comme étant équivalent à this. C'est ainsi que disparaît indirectement le caractère statique de la méthode et c'est ce qui permet d'accéder aux variables privées de la classe.
Dans le cas où on veuille uniquement pointer vers une fonction statique en dehors d'une classe, la méthodologie est identique mais il n'est plus nécessaire d'échanger le numéro d'instance en paramètres.
En cas de problème et pour plus d'informations sur les pointeurs de fonctions, vous pouvez consultez cette page en anglais.