11 | 12 | 2017

Python Wrapper

Erstellen eines C++ - Wrappers für Python am Beispiel der libdigitaltv

Einführung

Dieser Artikel beschreibt die Vorgehensweise bei der Erstellung eines C++-Wrappers für Python mit Hilfe der PyCXX. Die Voraussetzung für die erfolgreiche Programmierung ist die Installation des PyCXX-Paketes. Dieses Paket kann unter http://cxx.sourceforge.net bezogen werden.

Installation des PyCXX-Paketes

Im Grunde gestaltet sich die Installation des PyCXX sehr einfach. Nach dem herunterladen wird es mit tar -xvzf <paket> entpackt und anschließend wird mit Hilfe von

python setup.py install

zurerst PyCXX installiert und danach am besten die Demos ebenfalls mit

cd Demos

python setup.py install

installiert. Sollte die eigene Python-Version mit Hilfe eines anderen Compilers übersetzt worden sein, das betrifft oft Windows-Versionen, dann kann die PyCXX mit Hilfe von

python setup.py build --compiler=mingw32 install

neu übersetzt und installiert werden. Hier in diesem Beispiel wird dann der mingw32-Compiler benutzt (g++ für Win32).

Das Erstellen einer Wrapper-Klasse

Damit ein "neuer" Datentyp für Python erzeugt werden kann, muss ein neues Modul vorbereitet werden. Dafür erzeugt man sich eine neue Datei mit folgendem Inhalt:

01 #include "CXX/Objects.hxx"
02 #include "CXX/Extensions.hxx"
03
04 #include <assert.h>
05
06 #include "ctsfile.hxx" // My Test file
07 #include <algorithm>
08 #include <iostream>
09
10 class digitaltv_module : public Py::ExtensionModule<digitaltv_module>
11 {
12 public:
13 digitaltv_module()
14 : Py::ExtensionModule<digitaltv_module>( "libdigitaltv" )
15 {
16 CTSFile::init_type();
17 std::cout << "Hello World" << std::endl;
18
19 add_varargs_method("CTSFile", &digitaltv_module::new_tsfile, "CTSFile()");
20 initialize( "documentation for the digitaltv module" );
21
22 //Py::Object c(Py::asObject(new CTSFile()));
23 }
24
25 virtual ~digitaltv_module()
26 {}
27
28 private:
29 Py::Object new_tsfile(const Py::Tuple &tsargs)
30 {
31 return Py::asObject(new CTSFile());
32 }
33 };
34
35 extern "C" void initlibdigitaltv()
36 {
37 #if defined(PY_WIN32_DELAYLOAD_PYTHON_DLL)
38 Py::InitialisePythonIndirectPy::Interface();
39 #endif
40
41 static digitaltv_module* libdigitaltv = new digitaltv_module;
42 }
43
44 // symbol required for the debug version
45 extern "C" void initlibdigitaltv_d()
46 {
47 initlibdigitaltv();
48 }

Die Zeilen 1 und 2 inkludieren die wichtigen CXX-Header-Dateien. In Zeile 10 wird unser Modul definiert. Es wird von der Template-Klasse Py::ExtensionModule abgeleitet. Im Konstruktor (Zeile 16 bis 22) werden die "Hauptfunktionen" des Modules definiert. Zu Beachten gilt hier, auch das Erzeugen einer Instanz aus Python heraus muss hier in dieser Modulklasse definiert werden. Python findet sonst die Klasse nicht. Hier ist besonders die Zeile 19 interessant.

add_varargs_method("CTSFile", &digitaltv_module::new_tsfile, "CTSFile()");

Hier wird definiert, wie in Python auf eine zukünftige CTSFile-Instanz zugegriffen wird. Sobald eine CTSFile-Instanz erzeugt werden soll, wird die Methode (ab Zeile 29) ausgeführt, in der dann ein reales CTSFile-Objekt erzeugt und zurückgeliefert wird. In Python würde man schreiben:

tsfile = libdigitaltv.CTSFile()

Das ganze kann natürlich nur funktionieren, wenn man eine entsprechende CTSFile-Klasse definiert hat. Dafür werden wie üblich zwei zusätzliche Dateien erstellt. Eine Header- und eine CPP (oder CXX) - Datei. Die möchte ich Ihnen an dieser Stelle nicht vorenthalten.

01 #ifndef C_TSFILE_H
02 #define C_TSFILE_H
03 #include "CXX/Extensions.hxx"
04
05 class CTSFile: public Py::PythonExtension<CTSFile>
06 {
07 public:
08 CTSFile()
09 {
10 std::cout << "CTSFile object created " << this << std::endl;
11 }
12
13 virtual ~CTSFile()
14 {
15 std::cout << "CTSFile object destroyed " << this << std::endl;
16 }
17
18 Py::Object _hello( const Py::Tuple &args )
19 {
20 return Py::String("Hello World!");
21 }
22
23 static void init_type(void);
24
25 // override functions from PythonExtension
26 virtual Py::Object repr();
27 virtual Py::Object getattr( const char *name );
28 Py::Object reference_count (const Py::Tuple& args)
29 {
30 return Py::Int(this->ob_refcnt);
32 }
33};
34 #endif

Im Grunde sieht diese Klasse fast aus, wie jede andere. Fast! Alle Wrapper-Klassen sind von der Klasse Py::PythonExtension abgeleitet. Auch hier wieder eine Containerklasse, analog der Modul-Klasse. Der Konstruktor und Destruktor ist nichts besonderes. Die Methode in Zeile 18 zeigt prinzipiell, wie mit Python gearbeitet wird. Alle Methoden, die in Python zur Verfügung stehen sollen, müssen ein Py::Object zurückliefern.

01 #include "ctsfile.hxx"
02
03 Py::Object CTSFile::getattr( const char *name )
04 {
05 return getattr_methods( name );
06 }
07
08 Py::Object CTSFile::repr()
09 {
10 return Py::String("I am glad to see you!");
11 }
12
13 void CTSFile::init_type()
14 {
15 behaviors().name("CTSFile");
16 behaviors().doc("reads a ts-file");
17 behaviors().supportRepr();
18 behaviors().supportGetattr();
19 behaviors().supportSequenceType();
20
21 add_varargs_method("reference_count", &CTSFile::reference_count);
22 add_varargs_method("hello", &CTSFile::_hello, "Say simply hello");
23 }

Die Methode init_type() stellt wieder die Verbindung zur Python-Welt her. Insbesondere Zeile 22 zeigt, dass unter Python die Methode hello benutzt wird, um die "Hello World" Nachricht abzurufen. In Wirklichkeit wird die C++-Methode _hello() aufgerufen.

Abschlußbetrachtung

Fehlt noch das Übersetzen dieses Wrappers. Hierzu empfehle ich ein Blick in mein CMake-File, welches sich in dem nun folgenden Beispiel-Archiv befindet. Laden Sie es sich herunter und experimentieren Sie selbst ein wenig mit dem Wrapper.

Hinweis:

Sollten Sie sich jetzt an eine Ihrer C++-Bibliothek wagen und Wrapper erstellen, dann achten Sie bitte darauf, dass die Dateinamen der Wrapper-Klassen sich von denen der Originalbibliothek unterscheiden. Sonst kommt es während des Linkens zu einem Fehler, oder schlimmer, Python meldet beim Import des Wrappers: undefined references ...!