I. Interfacer Hello World▲
La première partie de ce tutoriel montre comment écrire un wrapper Boost.Python simple pour une classe C++, et peut-être plus important encore, comment tout avoir en main pour compiler et lier le tout convenablement.
I-A. Classe C++ simple▲
Pour commencer, considérons une classe C++ très basique, que nous mettrons dans Bonjour.hpp :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
#include
<iostream>
#include
<string>
class
Bonjour
{
// Attributs privés
std::
string m_msg;
public
:
// Constructeur
Bonjour(std::
string msg):m_msg(msg) {
}
// Méthodes
void
greet() {
std::
cout <<
m_msg <<
std::
endl; }
// Accesseurs/Mutateurs pour les attributs
void
set_msg(std::
string msg) {
this
->
m_msg =
msg; }
std::
string get_msg() const
{
return
m_msg; }
}
;
I-B. Interface Boost.Python▲
Notre objectif sera d'utiliser Boost.Python pour inclure cette classe dans un module, que nous appellerons pylib, qui sera importable directement depuis Python. Pour cela, Boost.Python fournit une API C++ qui nous permet de déclarer les classes et les fonctions que nous souhaitons exporter vers Python. Ces déclarations sont faites dans un fichier d'interface .cpp, que nous appellerons pylib.cpp :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
#include
<boost/python.hpp>
#include
"Bonjour.hpp"
using
namespace
boost::
python;
BOOST_PYTHON_MODULE(pylib)
{
class_<
Bonjour >
("Bonjour"
, init<
std::
string>
())
.def("greet"
, &
Bonjour::
greet)
.add_property("msg"
, &
Bonjour::
get_msg, &
Bonjour::
set_msg);
}
Décryptons ce qui se produit ici. La macro BOOST_PYTHON_MODULE déclare un module Python qui sera nommé pylib. Nous pourrons ajouter par la suite des classes et des fonctions à ce module en ajoutant les déclarations respectives entre les parenthèses.
Pour ajouter une classe, on crée un nouvel objet class_<>. Le modèle de cet objet est la classe que nous voulons exporter ; dans notre cas, class_<Bonjour> va encapsuler notre classe C++ vers Python. Le premier argument du constructeur de class_<>() est le nom que nous voulons utiliser pour cette classe dans Python ; ici on utilisera le même qu'en C++, « Bonjour », mais ce n'est pas obligatoire. Le second argument est utilisé pour définir quel constructeur C++ utiliser. Ici nous n'avons qu'un seul constructeur C++, mais une classe C++ peut en avoir plusieurs qui diffèrent de par leur prototype. Dans le but d'identifier celui à exporter, nous utilisons l'objet init<> dont le modèle correspond au prototype du constructeur, ici un simple argument de type std::string.
Déclarer une classe de cette manière créera simplement une classe Python vide : nous devons ensuite déclarer les méthodes et les attributs que nous voulons exporter du C++. Pour ajouter une méthode, on utilise la fonction def(), qui prend en premier argument le nom de la méthode Python, et en second argument une référence vers la méthode C++ actuelle. Enfin, on ajoute une propriété à notre classe Python à l'aide de add_property(), afin de pouvoir interroger et modifier le contenu du message d'accueil. Le premier argument de cette fonction est le nom de la propriété Python, les arguments suivants sont des références à l'accesseur et au mutateur de notre implémentation C++.
Voir cette page pour plus d'informations sur la manière d'exporter des classes.
I-C. Compilation avec CMake▲
Maintenant que le fichier d'interface est écrit, il faut le compiler. Ce processus de compilation implique en particulier un lien vers les bibliothèques Python et Boost.Python, ce qui peut s'avérer très pénible (en particulier sous MacOS X). Afin d'avoir une compilation aussi simple que possible, je vais utiliser CMake pour repérer le compilateur C++ par défaut, l'interpréteur Python, et télécharger et générer les modules Boost requis.
Il faut en premier lieu installer CMake. Sur une machine Ubuntu cela donne :
$
sudo apt-get install cmake
Vous pouvez aussi installer n'importe quel autre gestionnaire de paquets disponible sur votre système. Si exécuté sur un cluster, vous pouvez avoir à l'installer localement depuis les sources, suivre ces instructions.
Maintenant, pour compiler notre module, il faut écrire un fichier CMakeLists.txt, qui spécifie les bibliothèques nécessaires à la compilation et définit les bibliothèques ou les exécutables à générer :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
cmake_minimum_required
(
VERSION 2
.8
)
include
(
ExternalProject)
# Ajout d'un module cmake personnalisé pour générer boost
list
(
APPEND CMAKE_MODULE_PATH "
${CMAKE_SOURCE_DIR}
/cmake/Modules/"
)
project
(
tutorial)
# Trouver les bibliothèques et interpréteurs python par défaut
find_package
(
PythonInterp REQUIRED)
find_package
(
PythonLibs REQUIRED)
include
(
BuildBoost)
# module personnalisé
include_directories
(
${Boost_INCLUDE_DIR}
${PYTHON_INCLUDE_DIRS}
)
link_directories
(
${Boost_LIBRARY_DIR}
)
# Génère et lie le module pylib
add_library
(
pylib SHARED pylib.cpp)
target_link_libraries
(
pylib ${Boost_LIBRARIES}
${PYTHON_LIBRARIES}
)
add_dependencies
(
mylib Boost)
# Ajuste le nom de la bibliothèque pour coller à ce qu'attend Python
set_target_properties
(
pylib PROPERTIES SUFFIX .so)
set_target_properties
(
pylib PROPERTIES PREFIX ""
)
Pour que ce fichier fonctionne, vous devez copier localement mon module CMake personnalisé pour Boost, et le placer dans un dossier nommé cmake/Modules dans le répertoire courant. Ce fichier indique à CMake de compiler notre interface pylib.cpp dans une bibliothèque qui sera nommée pylib.so. Notez qu'il n'est pas nécessaire pour vous d'avoir Boost installé sur votre système, il sera téléchargé et compilé automatiquement par CMake afin de correspondre à votre configuration.
Dernière étape, la compilation :
$
mkdir build # Créé un fichier pour CMake pour faire cela
$
cd build
$
cmake .. # Lance la configuration CMake et génère un MakeFile
$
make pylib # Génère la bibliothèque
Et c'est tout. Ceci devrait générer un fichier nommé pylib.so, qui est un module Python directement chargeable depuis l'interpréteur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
In [1
]: from
pylib import
Bonjour
In [2
]: b =
Bonjour
(
"Hello World"
)
In [3
]: b.greet
(
)
Hello World
In [4
]: b.msg =
"Bonjour tout le monde"
In [5
]: b.greet
(
)
Bonjour tout le monde
In [6
]: b.msg
Out[6
]: 'Bonjour tout le monde'
Voilà l'idée générale pour écrire et interfacer avec CMake. Voir la documentation Boost.Python pour plus de détails.
II. Génération automatique d'interface avec Py++▲
On peut cependant remarquer à l'aide de l'exemple ci-dessus que pour encapsuler une bibliothèque C++ entière avec Boost.Python, il faut déclarer chaque méthode et propriété de chaque classe C++, ce qui peut demander beaucoup de travail. Heureusement, ce travail ne doit pas obligatoirement être réalisé par un humain. On peut en effet avoir recours à des générateurs de code automatiques, qui vont analyser vos fichiers C++ et créer automatiquement les déclarations Boost.Python correspondantes.
Le générateur d'interface officiel pour Boost.Ptyhon était auparavant Pyste : il n'est malheureusement plus maintenu et il a été supprimé des versions actuelles de Boost. Il a été remplacé par un projet différent nommé Py++.
Attention cependant : installer et utiliser Py++ en 2017 n'est pas une mince affaire, car la plupart de la documentation et des ressources que vous trouverez ne sont pas à jour. La documentation officielle fait référence à une branche stable du projet, depuis 2008. Heureusement, le développement de Py++ n'est pas totalement à l'arrêt : une version à jour est maintenue en tant que partie d'Open Motion Planning Library OMPL.
II-A. Installations requises▲
Py++ repose sur CastXML (anciennement GCC-XML), un outil qui analyse les fichiers d'en-tête C++ dans un arbre XML, et sur pygccxml pour générer les fichiers de sortie XML.
CastXML est disponible en tant que paquet au moins pour Ubuntu 16.04+ et MacPorts. Pour l'installer :
$
sudo apt-get install castxml
Pygccxml peut être installé avec PyPl en utilisant pip :
$
pip install --user pygccxml
Enfin, une version de Py++ peut être installée depuis l'OMPL bitbucket repository (depuis avril 2017) :
$
pip install --user https://bitbucket.org/ompl/pyplusplus/get/1
.7
.0
.zip
II-B. Écrire un script Py++▲
Py++ est une bibliothèque que vous pouvez utiliser dans un code Python pour analyser les fichiers sources de votre projet C++ et générer l'interface adéquate. Voici un exemple d'un tel script, qui va créer automatiquement l'interface pour votre classe C++ dans un nouveau module nommé pylib_auto (pour le différentier du précédent). Nous appellerons ce script pylib_generator.py :
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.
#!/usr/bin/python
from
pygccxml import
parser
from
pyplusplus import
module_builder
# Configurations que vous pouvez avoir à changer sur votre système
generator_path =
"/usr/bin/castxml"
generator_name =
"castxml"
compiler =
"gnu"
ompiler_path =
"/usr/bin/gcc"
# Créé une configuration pour CastXML
xml_generator_config =
parser.xml_generator_configuration_t
(
xml_generator_path=
generator_path,
xml_generator=
generator_name,
compiler=
compiler,
compiler_path=
compiler_path)
# Liste de tous les fichiers d'en-tête de votre bibliothèque
header_collection =
["Bonjour.hpp"
]
# Analyse les fichiers sources et créé un objet module_builder
builder =
module_builder.module_builder_t
(
header_collection,
xml_generator_path=
generator_path,
xml_generator_config=
xml_generator_config)
# Détecte automatiquement les propriétés et les accesseurs/mutateurs associés
builder.classes
(
).add_properties
(
exclude_accessors=
True
)
# Définit un nom pour le module
builder.build_code_creator
(
module_name=
"pylib_auto"
)
# Écrit le fichier d'interface C++
builder.write_module
(
'pylib_auto.cpp'
)
Ce code est explicite par lui-même : la partie la plus importante est la création du module_builder qui prend une liste de fichiers d'en-tête C++ à analyser. Voir la documentation Py++ pour plus d'information sur la manière de personnaliser finement le processus (ce qui peut être nécessaire pour des projets plus complexes).
Nous pouvons désormais analyser notre code source en exécutant ce script :
$ python pylib_generator.py
Si tout ce passe comme prévu, ce script devrait créer un nouveau fichier d'interface pylib_auto.cpp. Pour compiler ce nouveau module, ajoutons simplement une nouvelle section à la fin de notre fichier CMakeLists.txt :
2.
3.
4.
5.
6.
7.
8.
# Génère et lie le module pylib_auto
add_library
(
pylib_auto SHARED pylib_auto.cpp)
target_link_libraries
(
pylib_auto ${Boost_LIBRARIES}
${PYTHON_LIBRARIES}
)
add_dependencies
(
pylib_auto Boost)
# Ajuste le nom de la bibliothèque pour coller à ce qu'attend Python
set_target_properties
(
pylib_auto PROPERTIES SUFFIX .so)
set_target_properties
(
pylib_auto PROPERTIES PREFIX ""
)
Il nous faut mettre à jour la configuration de CMake et compiler :
2.
3.
$
cd build
$
cmake ..
$
make pylib_auto
Nous pouvons désormais tester notre module nouvellement créé :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
In [1
]: from
pylib_auto import
Bonjour
In [2
]: b =
Bonjour
(
"Hello World"
)
In [3
]: b.greet
(
)
Hello World
In [4
]: b.msg =
"Bonjour tout le monde"
In [5
]: b.greet
(
)
Bonjour tout le monde
In [6
]: b.msg
Out[6
]: 'Bonjour tout le monde'
Et c'est tout, nous avons créé une interface pour notre code sans rien spécifier manuellement, et le comportement est le même.
III. Pour aller plus loin▲
L'objectif de ce tutoriel était d'illustrer les bases de Boost.Python : ce n'était qu'un survol. Malheureusement, la documentation autour de Boost.Python est particulièrement mince, mais voici quelques liens qui pourraient vous être utiles :
Oh, et ai-je mentionné que Boost.Python avait désormais un support officiel pour l’interfaçage avec Numpy, via Boost.Numpy ?
- Documentation Boost.Numpy (fait partie de Boost.Python depuis 1.63)
IV. Remerciements▲
Nous remercions Cosinus(x) pour la traduction de ce tutoriel, Pyramidev pour sa relecture technique et naute pour sa relecture orthographique.