Table of Contents
Passerelle C++/Python
Cette section ne concerne que les développeurs qui souhaitent comprendre la manière dont les fonctionnalités Triskele peuvent être invoquées depuis un programme Python.
L'explication s'appuie sur un exemple d'une fonction et d'une classe écrite en C++. Les codes sources sont dans un répertoire :
drwxr-xr-x 3 4096 build -rw-r--r-- 1 825 CMakeLists.txt drwxr-xr-x 2 4096 __pycache__ -rw-r--r-- 1 681 pywrapper.cpp -rw-r--r-- 1 325 test.cpp -rw-r--r-- 1 228 test.hpp -rwxr-xr-x 1 336 test.py | répertoire pour créer les exécutables Règles de fabrication des binaires répertoire créé automatiquement par python (accélération) code passerelle code C++ interface C++ programme python |
Ensemble des sources boostpythontest.tgz (5 fichiers)
Code C++
L'interface C++ est simple mais couvre plusieurs situations :
- appel de fonction (hors classe)
- construction d'un objet
- surcharge de constructeur
- accès d'attribut en lecture/écriture (par valeur et par méthode)
- invocation de méthode
L'interface se trouve dans le fichier entête C++ (headers
)
#include<string> using namespace std;
Déclaration d'une fonction
string getMsg ();
Déclaration d'une classe
class Mem { private: string msg; public: int id; Mem (string msg); Mem (string msg, int id); virtual ~Mem (); void set (string msg); string get (); };
Le programme proprement dit (les données initiales et le comportement) se trouve dans un fichier cpp
.
#include <iostream> #include "test.hpp"
La trace sera affichée au moment de l'appel dans l'environnement C++.
string getMsg () { cerr << "coucou" << endl; return "hello, world"; }
Exemple de constructeurs surchargés.
Mem::Mem (string msg) : Mem (msg, 0) { } Mem::Mem (string msg, int id) : msg (msg), id (id) { } Mem::~Mem () { }
Les accesseurs pour être utilisés pour simuler l'accès à un attribut python.
void Mem::set (string msg) { this->msg = msg; } string Mem::get () { return msg; }
Code Python
La première ligne permet de sélectionner le bon interpréteur.
#! /usr/bin/python3
Le nom du module correspond au nom de fichier suivi de l'extension .so
.
from libtest import getMsgPy, MemPy
Appel d'une fonction
print (getMsgPy ())
Création d'un objet C++ avec appel de constructeur.
word = MemPy ('A')
Liaison lecture/écriture d'un attribut.
word.id = 1; print (f"m:{word.getPy ()} id:{word.idPy}") word.setPy ('B') print (f"m:{word.getPy ()} id:{word.idPy}") word2 = MemPy ('C', 1) print (f"m:{word2.getPy ()} id:{word2.idPy}")
Utilisation des accesseurs.
word2.msgPy = word.msgPy+"2" print (word2.msgPy)
Passerelle
La liaison ce fait grâce à la bibliothèque Boost avec un ensemble de déclarations.
#include <boost/python.hpp> #include "test.hpp" using namespace boost::python;
libtest
est le nom du fichier sans l'extension .so
. Si l'on fait deux bibliothèques (par exemple libtest.so
et libtestdebug.so
), il faudra deux déclarations.
BOOST_PYTHON_MODULE (libtest) {
Le nom entre guillemets est l'identifiant utilisé dans le programme python. Il peut être le même qu'en C++, mais ici cela permet de montrer la différence entre le contexte C++ et Python.
L'identifiant getMsg
est directement un pointeur sur la fonction.
def ("getMsgPy", getMsg);
La déclaration d'un constructeur est optionnelle.
class_<Mem> ("MemPy", init<string>())
On peut même ajouter la déclaration d'autres constructeurs.
.def (init<string, int>())
Comme l'invocation de la méthode .def
retourne un pointeur sur le même objet, on peut faire une cascade d'appel (il ne faut pas de “;” entre chaque).
Un pointeur sur un membre (ici une méthode) de la classe Men
permet la liaison avec le code C++.
.def ("getPy", &Mem::get) .def ("setPy", &Mem::set)
La liaison entre l'identifiant Python et l'homologue C++ permet d'utiliser la variable Python à droite ou à gauche d'un signe égale (on dit leftvalue
lorsque l'on peut affecter une valeur)
.def_readwrite ("idPy", &Mem::id)
Ici on a recours à des accesseurs. Il faudra ne pas oublier le “;” pour clore la série de déclaration.
.add_property ("msgPy", &Mem::get, &Mem::set) ; }
Compilation
Pour simplifier la création de la librairie C++ qui sera vu comme un module Python, nous avons utilisé cmake
.
cmake_minimum_required (VERSION 3.5) set (PROJ_NAME "test") project (${PROJ_NAME}) set (CMAKE_CXX_STANDARD 11) set (CXX_STANDARD_REQUIRED ON)
Il est important de produire une bibliothèque dynamique (SHARED
) pour qu'elle soit chargée à la volée dans l'environnement Python.
set (BUILD_SHARED_LIBS ON)
Nous allons dépendre d'autres bibliothèques.
find_package (PythonLibs 3 REQUIRED) find_package (Boost REQUIRED python3 system chrono)
Nous créons une liste de sources pour constituer notre bibliothèque.
list (APPEND libTestSrc "test.cpp" "pywrapper.cpp" )
Répertoire où l'on trouve les entêtes nécessaires à la compilation.
include_directories ( PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} )
Compilation de tous les modules C++ et préparation de l'archive.
add_library (test SHARED ${libTestSrc} )
Cette directive est indispensable. Sans quoi, on aura l'illusion que tout est fait, mais au moment du chargement du module par python, il ne sera pas trouver les dépendance. Il faudrait alors charger dans l'environnement python les autres bibliothèques (Boost, pythonLib).
target_link_libraries (test ${PYTHON_LIBRARIES} ${Boost_LIBRARIES} )
Installation du package libboost-python-dev
.
sudo apt-get install -y libboost-python-dev
Nous allons créer les binaires dans un sous-répertoire build
.
mkdir build/ cd build/ cmake .. make -j $(nproc)
L'interpréteur va rechercher les modules python à importer (des bibliothèques dynamiques) en fonction d'un chemin qui peut contenir des répertoires relatifs ou absolus.
Nous avons indiqué “.” car nous exécutons le programme dans le sous-répertoire. Si nous avions exécuté depuis l'endroit où se trouve le programme Python, il aurait fallu indiquer “./build/”.
export PYTHONPATH=".:/usr/local/lib/python3/"
Nous pouvons explicitement rendre le programme python exécutable et l'invoquer.
chmod a+x ../test.py ../test.py
Voici le résultat en commençant par la trace réalisée dans l'environnement C++.
coucou hello, world m:A id:0 m:B id:0 m:C id:1 B2