Interfaçage entre C++ et Python Patrick Hubert Chef Technique @ Ubisoft 2014-03-20 Titre alternatif Accélérer la boucle de rétroaction par l'utilisation d'un langage interprété dans le contexte d'une application écrite avec un langage compilé. Mon expérience Études à l’Université de Sherbrooke (1er cycle) Math-Info (où j’ai rencontré Patrice) Développement logiciel Discreet Logic / Alias / Autodesk Développement de moteurs de jeux vidéo Artificial Mind & Movement / Behaviour Développement Web Whisky Echo Bravo Développement de composantes réseaux pour des jeux Quazal / Ubisoft Le C++ et Python Description sommaire des deux langages… Le langage C++ Conçu par Bjarne Stroustrup en 1983 Langage compilé avec phase d’édition de liens Fortement typé Statique Performant et rapide, descendant du langage ‘C’ Gestion de la mémoire laissée au programmeur Le langage Python Conçu par Guido Van Rossum en 1991 Langage interprété Se base sur l’interface plutôt que le type Dynamique Moins rapide, car interprété Gestion de mémoire automagique (Garbage Collector / Ref Counting) Le développement en C++ Les structures et fonctions sont définies explicitement dans les fichiers sources Un cycle comprend habituellement les étapes suivantes: 1. Édition du code 2. Compilation des sources 3. Éditions des liens 4. Démarrage de l’application et validation 5. Arrêt de l’application et retour a 1 Le développement en Python Ce qui à été dit pour le C++ est aussi valide… Par contre, les structures et fonctions peuvent aussi être créées ou modifiées alors que l’application fonctionne (‘First-class objects’) Un cycle peut être réduit aux étapes suivantes: 1. Édition du code 2. Démarrage de l’application et interprétation 3. Modifications et mises-à-jour des structures existantes dynamiquement, et/ou retour à 1 Le compilateur C++ N’est qu’une des composantes requises pour concevoir une application L’éditeur de lien et le débogueur sont deux autres applications indépendantes requises L’application résultante est le quatrième maillon de la chaîne (le premier étant le code source) L’interpréteur Python Est à la fois compilateur, éditeur de liens, débogueur et l’application résultante Il en existe une implémentation C (CPython), Java (Jython) et C#/.NET (IronPython) L’interpréteur Python Évaluation visible à l’usager: Interface interactive via la ligne de commande Interprétation immédiate du code % python Python 2.5 (r25:51908, Mar 13 2007, 08:13:14) [GCC 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)] on cygwin Type "help", "copyright", "credits" or "license" for more information. >>> print "Bonjour!" Bonjour! >>> L’interpréteur Python Évaluation invisible à l’usager: Analyse syntaxique et interprétation des modules Création dynamique des classes, fonctions, etc. % python Python 2.5 (r25:51908, Mar 13 2007, 08:13:14) [GCC 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)] on cygwin Type "help", "copyright", "credits" or "license" for more information. >>> import Bibliotheque >>> print Bibliotheque.Fonction( 'Foobar' ) 6 >>> Comparaison de code Les langages offrent des bases communes et des éléments structurels similaires: Fonctions Classes Bibliothèques ou modules Exemple de fonction C++ #include <string> namespace Bibliotheque { size_t Fonction( const std::string& pTexte ) { return pTexte.length(); } } // Ne fonctionne qu’avec des arguments de type // std::string Exemple de fonction Python def Fonction( pTexte ): # equivalent a ‘len( pTexte )’ return pTexte.__len__() # # # # # # # # Fonctionne aussi bien avec un paramètre de type string qu'une liste, donne '3' dans les 2 cas Fonction( "abc" ) et # Fonction( [ 1, 2, 3 ] ) '__len__()' est présent dans toutes les interfaces de séquences Python. 'template <typename tType> size_t Fonction( tType );' se résout statiquement à la compilation, au contraire de la version Python qui l'est dynamiquement Utilisation de bibliothèques en C++ #include <Bibliotheque.hpp> #include <iostream> int main() { std::string lString( "abc" ); std::cout << Bibliotheque::Fonction( lString ) << std::endl; return 0; } // S'assurer bien sur de mentionner la // Bibliothèque lors de l'édition des liens Utilisation de modules en Python import Bibliotheque print Bibliotheque.Fonction( 'abcd' ); Une classe en C++ #include <Bibliotheque.hpp> #include <string> struct Parent {}; class Enfant : public Parent { std::string mChaine; public: Enfant( const std::string& pChaine ) : mChaine( pChaine ) {} size_t Valeur() const { return Bibliotheque::Fonction( mChaine ); } }; Une classe en Python import Bibliotheque class Parent: pass class Enfant( Parent ): def __init__( self, pChaine ): self.mChaine = pChaine def Valeur( self ): return Bibliotheque.Fonction( self.mChaine ) Métissage des deux langages Il existe trois façons d’interfacer C++ et Python: 1. Écrire de nouveaux modules pour Python en C++ (pour des raisons de performances) 2. Intégrer l’interpréteur Python à même une application (pour exposer de la fonctionnalité existante, ou même en ajouter) 3. Une combinaison des deux options précédentes Quels sont les avantages? Combiner la vitesse de C++ avec la facilité et la rapidité de développement en Python Pour modifier le code de l’application, il faut habituellement l’arrêter. De même pour un plug-in ou une DLL/DSO En Python, ce n’est pas le cas. La construction dynamique des objets permet une introspection et modification de ces derniers sans avoir redémarrer l’application Considérations importantes Le langage C++ est fortement typé L’interaction entre Python et le C++ devra tenir compte de cette contrainte et s’assurer de la compatibilités des objets et paramètres (interface vs type statique) Les outils que nous considérerons pour le travail devront offrir cette protection. (e.g. conversion automatique, validation, émission d’erreurs, etc.) La couche d’interface entre C++ et Python L’interpréteur est implémenté en C Elle peut être écrite manuellement, ce qui laisse beaucoup de place à l’erreur. Elle peut aussi être écrite a l’aide de classes utilitaires, ou bien généré à partir d’un IDL (Interface Definition Language) Nous présenterons 3 alternatives: manuelle, Boost.Python (classes et templates) et SWIG (générateur) Manuelle Demande une bonne connaissance de l’interface C de Python Peu ou pas de validation, donc possibilité d’erreurs Requiert beaucoup de maintenance, pour un API qui change beaucoup Manuelle (code 1/2) #include <python.h> #include <Bibliotheque.hpp> static PyObject* Fonction_imp( PyObject* pSelf, PyObject* pArgs ) { const char* lChaine = 0; if( !PyArg_ParseTuple( pArgs, "s", &lChaine )) return 0; size_t lResult = Bibliotheque::Fonction( lChaine ); return Py_BuildValue( "i", lResult ); } Manuelle (code 2/2) static PyMethodDef ManuelleMethods[] = { { "Fonction", Fonction_imp, METH_VARARGS, "" }, {0, 0, 0, 0} }; PyMODINIT_FUNC initManuelle() { Py_InitModule( "Manuelle", ManuelleMethods ); } Boost.Python Fait partie de la bibliothèque Boost Met à notre disposition des templates de classes pour définir les classes, méthodes et fonctions à exposer vers Python Utilise le ‘Run-Time Type Identification’ (RTTI) pour générer les interfaces du bon type Dépend aussi énormément des algorithmes du compilateur pour déduire les paramètres de templates. Offre des outils pour communiquer avec les objets Python indigènes Boost.Python (code) #include <boost/python.hpp> using namespace boost::python; #include <Bibliotheque.hpp> BOOST_PYTHON_MODULE( Boost ) { def( "Fonction", Bibliotheque::Fonction ); } SWIG ‘Simplified Wrapper and Interface Generator’ À partir d’un fichier de définition d’interface créé par le programmeur, génère du code Python et C/C++ qui fait le lien entre les deux langages Demande une étape de manipulation supplémentaire, pour la génération du code intermédiaire. Est très rapide et relativement facile à utiliser, tout en offrant une série de paramétrisations SWIG (code) %module Swig %include "std_string.i" %{ #include <Bibliotheque.hpp> using namespace Bibliotheque; %} size_t Fonction( const std::string& pTexte ); Expérience personnelle (Alias) Exposition de l’API d’un SDK orienté-objet en C++ en Python, le OpenRealitySDK de MotionBuilder Correspondance 1 à 1 des deux langages dans 90% des cas. Pour certaines méthodes spéciales, telle 'operator=‘, nous nous devions de trouver des alternatives. Nous avons utilisé Boost.Python Le code C++ de Boost.Python était généré à partir de fichiers XML décrivant les classes C++ de notre SDK Expérience personnelle (Behaviour) Ré-écriture en C++ de modules jugés peu performants dans le contexte d’une application en Python. En utilisant du profilage, des modules Python peu performants ont été identifiés. Ces derniers ont été recodé en C++ et nous avons observé des gains significatifs de performance. Nous avons utilisé Boost.Python et Swig. Expérience personnelle (Quazal) Ajout de modules écrits en Python à un programme serveur en C++. Permet de facilement d’ajouter et de tester de nouveaux services sur le serveur. Les services peuvent être activés et désactivé sans avoir à redémarrer le serveur. Nous avons utilisé Swig. Points positifs Accélère le cycle de développement de façon significative Permet d’exposer une interface à l’application auprès d’un plus grand public Python ne requiert pas un environnement de développement (MSDEV, GCC, XCode, etc.) Permet la création de nouvelles fonctionnalités en Python exclusivement, sur lesquelles on peut bâtir un nouvel environnement pour des gens moins familiers avec la programmation Points négatifs La gestion de la couche d’interface, Boost.Python ou bien SWIG, peut être onéreuse et ardue. Surtout si l’API à exposer est complexe et évolue encore Trouver une façon d’automatiser la génération de la couche d’interface n’est pas facile Les temps de compilation de la couche utilisant Boost.Python sont importants Points à surveiller S’assurer qu’une instance d’objet C++ donnée est encapsulée par un seul objet Python. Certaines méthodes d’objets n’ont pas de correspondance en Python, tel 'operator=‘ En portant un SDK C++ on risque d’avoir un API Python qui laisse transparaître ses origines. Modifier les interfaces des objets pour se conformer à ce qui se fait sur Python Ajouter les éléments syntaxiques nécessaire pour éviter que le code Python ressemble a du C++ (conversion, etc.) Suggestions A moins de n’avoir que quelque fonctions simples à exposer à Python, ne pas utiliser la méthode manuelle SWIG est la solution la plus facile pour les projets de moyenne envergure Boost.Python est excellent pour interfacer directement avec Python, en plus d’offrir de bons outils. Par contre il y a un impact important sur les temps de compilations. Vos questions ? C++ Python Performances Jeux vidéos Consoles Références Python: http://python.org/ Boost: http://boost.org/ Swig: http://www.swig.org/ Commentaires et questions: [email protected]