Bonjour à tous,
Voilà plusieurs années que je programme différents projets dans lesquels j'ai besoin d'un langage de script. Après quelques recherches, mon choix c'est tourné vers Lua, principalement pour sa syntaxe proche de celle du C++ et pour sa simplicité.
Le seul soucis, c'est que Lua a été programmé en C. Autant la syntaxe du langage est simple, autant son interface en C est relativement pénible à utiliser, même si je doute que l'on puisse faire beaucoup mieux.
J'ai donc pris le temps d'écrire un wrapper en C++ dont le but est d'encapsuler tous les appels de fonctions C de Lua et qui tire profit des capacités du C++. Pour le moment, seule une partie des fonctions de la bibliothèque originale sont supportées, mais je n'ai pas encore éprouvé le besoin d'utiliser les autres De plus, il reste possible à tout moment de récupérer un pointeur vers la lua_State sous-jacente et d'utiliser comme on le souhaite les fonctions C dessus. En bref : que du bonus. Les sources ci-dessous contiennent donc une classe "centrale", qui wrappe le pointeur vers la lua_State. Elle est sobrement nommée lua::state.
Mais ce n'est pas tout ! On a très souvent besoin d'ajouter des fonctions supplémentaires au langage Lua, qui font appel à des fonctions du programme qu'on développe. On appelle ça des "glues". Le principe est assez simple (les arguments de la fonction sont déposés en haut du stack Lua, on les lit, puis on pousse les valeurs de retour sur le stack, que Lua gère ensuite), mais est très sujet aux erreurs. En particulier, le gestion des types d'entrée peut être pénible. J'ai donc également programmé une classe utilitaire nommée lua::function qui se charge de tout : vérification des types en entrée, gestion des valeurs de retour, etc. La syntaxe est je pense assez simple :
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 // La signature obligatoire des "glues" int set_texture(lua_State* pLua) { // On créé d'abord la classe d'aide. // Le nom sera utilisé pour les messages d'erreurs. // Le nombre à la fin spécifie le nombre total de // valeurs que la fonction est sensée retourner. // Si vous en donnez moins, la fonction complètera // elle même avec des 'nil' (ce paramètre peut être // ommis). lua::function mFunc("set_texture", pLua, 2); // ... puis on définit les paramètres qu'on attend. // Par exemple ici, on peut prendre soit une chaine : mFunc.add(0, "texture", lua::TYPE_STRING); // ... ou, mFunc.new_param_set(); // ... quatre nombres, mFunc.add(0, "red", lua::TYPE_NUMBER); mFunc.add(1, "green", lua::TYPE_NUMBER); mFunc.add(2, "blue", lua::TYPE_NUMBER); // ... dont un qui est optionel. mFunc.add(3, "alpha", lua::TYPE_NUMBER, true); // Eventuellement, si on a une grande liste d'arguments // et qu'on veut que le Xième puisse prendre deux // types différents, on a le droit d'écrire : mFunc.add(X, "blabla", lua::TYPE_STRING); mFunc.add(X, "blabla", lua::TYPE_NUMBER); // Il faudra alors vérifier soi-même le type obtenu. // On vérifie ensuite si les paramètres sont bons. if (mFunc.check()) { // On regarde quel set de paramètre a été fourni if (mFunc.get_param_set_rank() == 0) { // Puis on extrait les données en conséquence std::string name = mFunc.get(0)->get_string(); // [...] } else { color mColor; // Ici on regarde si le paramètre optionnel a été fourni if (mFunc.is_provided(3)) { mColor = color( mFunc.get(3)->get_number(), mFunc.get(0)->get_number(), mFunc.get(1)->get_number(), mFunc.get(2)->get_number() ); } else { mColor = color( mFunc.get(0)->get_number(), mFunc.get(1)->get_number(), mFunc.get(2)->get_number() ); } // [...] } } // Ensuite on envoie deux valeurs : double toto = 1.0; mFunc.push(toto); std::string pouet = "hello"; mFunc.push(pouet); // ... et on termine en appelant on_return, // qui retourne simplement le nombre total de // valeurs retournées, comme Lua le veut. return mFunc.on_return(); }
Le tout vient avec un type variant lua::var qui permet de stoker dans une même variable C++ un objet de type nombre, string, booléen, ou autre, ce qui simplifie grandement la vie dans certains cas (le code est inspiré de la classe "variant" présentée sur ce site !). Les deux autres classes supportent ce type nativement.
Pour finir, j'ai aussi inclus un petit bout de code qui n'est pas de moi, nommé "Lunar", qui permet de gérer des classes en parallèle entre Lua et C++ de manière assez simple. Je n'ai pas trouvé de version mise à jour, et je n'ai pas les connaissances nécessaires en Lua pour l'upgrader. À noter donc : tout le code est compatible avec Lua 5.1.4, mais par la 5.2 !
Voilà pour la présentation, je pense n'avoir rien oublié.
Dans la version pré-compilée, vous trouverez un dossier "doc" avec la documention doxygen pré-générée. Les commentaires sont de toute façon toujours dans les sources, donc vous pouvez la générer vous même si vous le souhaitez.
Le tout se compile en principe sans problème avec MinGW sous Windows ou avec g++ sous Linux, en activant le support du C++11 (nécessite gcc 4.5 au minimum). Les projets de compilation pour Code::Blocks sont fournis, mais rien de plus. La bibliothèque "luapp" se base sur une petite bibliothèque utilitaire "utils" qu'il faudra aussi compiler (elle propose un pointeur à compteur de référence et son weak pointer associé, quelques fonctions raccourcis pour la gestion des fichiers et des chaînes de caractères).
C'est open source et libre, distribué sous licence GNU.
J'espère que ça pourra servir à d'autres !
Normalement, le tout est bien rôdé puisque je m'en sers depuis longtemps. Seulement, j'ai commencé a me servir d'un framework un peu lourdingue dont j'essaye maintenant de me passer. J'ai donc dû re-traduire tout pour éliminer les dépendances. En conséquence, il peut y avoir quelques accrocs, mais rien de trop grave je pense.
N'hésitez pas à intervenir ici si vous avez des soucis ou si certains points ne sont pas clairs.
Les fichiers, pour la v1.000 :
Partager