====== 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 {{ :python:boostpythontest.tgz |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'')
{{ :python:test.hpp |}}
#include
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''.
{{ :python:test.cpp |}}
#include
#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.
{{ :python:test.py |}}
#! /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.
{{ :python:pywrapper.cpp |}}
#include
#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_ ("MemPy", init())
On peut même ajouter la déclaration d'autres constructeurs.
.def (init())
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''.
{{ :python:cmakelists.txt |}}
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
$
${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