Python Embedding mit boost::python
In vielen Fällen ist es wünschenswert, dem Benutzer einer Software die Möglichkeit zu geben, mittels einer Skriptsprache Erweiterungen zur Anwendung hinzuzufügen oder deren Laufzeitverhalten zu steuern oder zu ändern. Das Einbetten einer Skriptsprache in die Applikation ist allerdings kein einfaches Vorhaben, da Daten und Datenstrukturen zwischen den Sprachkontexten ausgetauscht werden müssen um der Skriptsprache Zugriff auf Objekte der Applikation zu ermöglichen und umgekehrt.
Dieser Artikel soll eine kurze Einführung in die Einbettung von Python in C++ Applikationen geben. Dabei wird die Library boost::python benutzt, die das Marshalling der Objekte erheblich vereinfacht (z.B. Kapselung der ansonsten notwendigen manuellen Verwaltung der Garbage Collection).
Betrachten wir ein sehr einfaches Python Modul mymodule.py mit nur einer einzigen Funktion:
def myfunction(): c = 1234 * 5678 print 'Result is', c
Um diese Funktion aus einem C++ Kontext heraus aufzurufen, schreiben wir folgendes minimales Programm:
#include <boost/python/import.hpp> #include <boost/python/call.hpp> using namespace boost::python; int main(int argc, char* argv[]) { Py_Initialize(); object mymodule = import("mymodule"); object dict = mymodule.attr("__dict__"); object func = dict["myfunction"]; func(); Py_Finalize(); return 0; }
Die Aufrufe von Py_Initialize() und Py_Finalize() sind notwendig, um den eingebetteten Python Interpreter zu initialisieren bzw. sicher herunterzufahren. Im nächsten Schritt wird zunächst das Modul importiert, das Dictionary des Moduls geholt und schließlich eine Referenz auf die gewünschte Funktion erzeugt. Mit func() wird diese anschließend aufgerufen.
Durch die Verwendung von boost::python werden die zurückgelieferten PyObject Pointer in object Objekten gekapselt, die sich um die Garbage Collection kümmern. Dadurch spart man sich gegenüber der nativen Python API das manuelle Freigeben der Pointer mittels Py_DECREF().
Auf manchen Systemen (z.B. Linux) funktioniert der Aufruf jedoch nicht wie gewünscht, da der eingebettete Python Interpreter das Modul nicht findet. Der Grund dafür ist, dass der Pfad, in dem das C++ Programm ausgeführt wird, nicht automatisch als Suchpfad für Python Module hinzugefügt wird. Die Lösung für dieses Problem ist meines Wissens nicht dokumentiert und besteht einfach darin, mittels PySys_SetArgv(1, progName) einen beliebigen Namen für das Programm zu setzen (oder einfach die argc und argv Parameter der main Methode zu benutzen). Das komplette Programm sieht dann beispielsweise folgendermaßen aus:
#include <boost/python/import.hpp> #include <boost/python/call.hpp> using namespace boost::python; int main(int argc, char* argv[]) { Py_Initialize(); char* progName[] = { const_cast<char*>("Embedded") }; PySys_SetArgv(1, progName); object mymodule = import("mymodule"); object dict = mymodule.attr("__dict__"); object func = dict["myfunction"]; func(); Py_Finalize(); return 0; }