Python meets C/C++, Teil 2: SWIG und pybind11

SWIG erstellt C/C++-Wrapper zur Kommunikation mit Python und anderen Sprachen. Das Framework pybind11 spielt seine Stärken beim Kommunizieren mit C++ aus.

Lesezeit: 18 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 15 Beiträge
Python
Von
  • Rainer Grimm
Inhaltsverzeichnis

Der erste Teil unserer Miniserie hat die Grundlagen für den Aufruf von C/C++-Bibliotheken und das direkte Schreiben von Erweiterungsmodulen gelegt, nun taucht der zweite Teil in einen anderen Bereich ein: Im Folgenden geht es um die Hilfsmittel, die das Bauen von Erweiterungen erleichtern. Python kennt einige Frameworks, die die notwendige Kommunikationsinfrastruktur automatisch erzeugen. Exemplarisch geht dieser Artikel auf den Simplified Wrapper and Interface Generator (SWIG) sowie auf pybind11 ein. Angesichts der Vielzahl existierender Tools scheint es geboten, sich auf zwei Möglichkeiten zu konzentrieren.

SWIG ist schon lange am Markt und verfügt über ein Alleinstellungsmerkmal: Es kann Bindung zu verschiedenen Programmiersprachen herstellen – beispielsweise erlaubt SWIG es, C/C++-Wrapper zur Kommunikation mit Python zu erzeugen. pybind11 hingegen kommt in der Community wohl am häufigsten zum Einsatz und wirkt besonders modern. Es glänzt vor allem dann, wenn es gilt, direkt mit C++ zu kommunizieren. Zum Abschluss folgt eine Auseinandersetzung mit der Einbettung von Python in C und C++.

Der Simplified Wrapper and Interface Generator (SWIG) erzeugt Interfaces, sodass C beziehungsweise C++ mit anderen Programmiersprachen kommunizieren können. SWIG blickt auf eine lange Geschichte zurück und wurde ursprünglich 1995 von David Beazley entwickelt, der in der Python-Community bekannt ist. Seine Absicht war es, eine einfache Schnittstelle für die Kommunikation zwischen C/C++ und Skriptsprachen zu erzeugen. Konsequenterweise spricht SWIG mehrere Sprachen wie beispielsweise C#, D, Java, JavaScript, Perl, Python, PHP oder Ruby.

Die Betrachtungen hier beschränken sich auf Python. SWIG unterstützt die Standards C99 sowie C++98 bis C++17 und dient als Wrapper zwischen Python und C beziehungsweise C++. Dabei konvertiert er die Werte zwischen Python und C/C++ vor- und rückwärts (s. Abb. 1).

SWIG als Wrapper zwischen Python und C/C++ (Abb.1)

SWIG unterstützt die Standards von ISO-C und ISO-C++ sowie alle ISO-C-Datentypen, Zeiger in allen Formen, globale Funktionen, Variablen und Konstanten. Darüber hinaus kommt er mit Strukturen, Unions, Aufzählungen sowie einfachen und mehrdimensionalenArrays zurecht. Im Bereich von ISO-C++ kennt SWIG alle C++-Datentypen und bietet Referenzen und Zeiger auf Attribute einer Klasse an. Obendrein beherrscht er objektorientierte Features wie Vererbung, Mehrfachvererbung und Virtualität. Funktionen und Operatoren lassen sich mit SWIG überladen. Zudem bietet SWIG die meisten C++11- und C++17-Features und lässt sich für Templates sowie für die Standard Template Library einsetzen.

Simplified Wrapper and Interface Generator (SWIG)

Die folgenden zwei Aufzählungen sollen einen kleinen Überblick über die von SWIG unterstützten Elemente liefern:

ISO-C

  • Alle ISO-C-Datentypen
  • Globale Funktionen, Variablen und Konstanten
  • Strukturen und Unions
  • Zeiger und Funktionszeiger
  • Arrays und mehrdimensionale Arrays
  • Typedefs
  • Callbacks
  • Aufzählungen

ISO-C++

  • Alle C++-Datentypen
  • Referenzen
  • Zeiger auf Mitglieder einer Klasse
  • Überladen von Funktionen und Operatoren
  • Virtualität
  • Statische Mitglieder
  • Namensräume
  • Templates einschließlich der partiellen und vollständigen Spezialisierung
  • Smart Pointers
  • Standard Template Library
  • Die meisten C++11- und C++17-Features

Der leichteste Einstieg in SWIG ist es, ein Erweiterungsmodul zu implementieren, was hier mit einem einfachen helloWorld-Beispiel demonstriert wird. Das Erzeugen eines Erweiterungsmoduls mit SWIG beginnt mit einer einfachen SWIG-Interfacedatei, die als hello.i im folgenden Listing dargestellt ist.

/* hello.i */

%module helloWorld
%{
#define SWIG_FILE_WITH_INIT
#include "helloWorld.h"
%}

extern void helloWorld();

Die Datei hello.i enthält den Namen des Moduls helloWorld und spezifiziert mit SWIG_FILE_WITH_INIT, dass die zu erzeugende Erweiterung eine Python-Erweiterung ist. Letztere fügt den Initialisierungscode für das Modul hinzu. Außerdem umfasst die Datei die Headerdatei der C-Quellen helloWorld.h und eine Deklaration der C-Funktion namens extern void helloWorld(). Das Kommando swig erzeugt mithilfe der Interfacedatei die zwei Dateien helloWorld_wrap.c und helloWorld.py, Abbildung 2 zeigt den dabei ablaufenden Build-Prozess.

Der SWIG-Build-Prozess (Abb. 2)

Während die Datei helloWorld_wrap.c der Low-Level-Wrapper ist, der mit der restlichen Applikation gelinkt wird, enthält die Python-Datei helloWorld.py den High-Level-Code, der sich aus Python importieren lässt. Das Modul benötigt nicht nur die dafür erforderlichen C- und Python-Wrapper, die durch swig erzeugt wurden, sondern auch die eigentliche C-Funktionalität. Das nächste Listing zeigt die Headerdatei helloWorld.h zu der korrespondierenden Quellcodedatei helloWorld.c.

#include <stdio.h>

void helloWorld();

Die Quellcodedatei ist bewusst einfach gehalten.

#include "helloWorld.h"

void helloWorld() {
    printf("Hello World\n");
}

Damit ist die Zielgerade erreicht. Jetzt gilt es nur noch, das Erweiterungsmodul helloWorld zu bauen und zu verwenden: Mit dem Python-Modul disutils sollte das leicht von der Hand gehen. Dazu ist lediglich die Konfigurationsdatei setup.py aus folgendem Listing notwendig.

from distutils.core import setup, Extension


helloWorld_module = Extension('_helloWorld',
                           sources=['helloWorld_wrap.c', 'helloWorld.c'],
                           )

setup (name = 'helloWorld',
       version = '1.0.0',
       author      = "Rainer Grimm",
       description = "Python Module helloWorld",
       ext_modules = [helloWorld_module],
       )

In dieser minimalen Konfigurationsdatei erzeugt die Zeile helloWorld_module = Extension(....) das Erweiterungsmodul, es besteht aus den zwei Dateien helloWorld.wrap.c und helloWorld.c. Per Konvention wird dem Erweiterungsmodul helloWorld der Unterstrich vorangestellt: _helloWorld. Darüber hinaus enthält die setup.py-Datei den Namen name des Moduls und die Attribute version, description, ext_modules sowie py_modules, den Dokumentationsstring, den Autor oder die Autorin und deren E-Mail-Adresse. Das Attribut ext_modules referenziert das bereits definierte Erweiterungsmodul helloWorld_module.

Mit der Konfigurationsdatei setup.py lässt sich das Erweiterungsmodul plattformunabhängig mit dem Befehl python setup.py build_ext --inplace erstellen. Der Screenshot in Abbildung 3 zeigt den CLI-Befehl auf Linux.

Erzeugen des Erweiterungsmoduls (Abb. 3)

Zu sehen sind die verwendete Python-Version (python), die Konfigurationsdatei (setup.py) und das Argument build_ext: Sie teilen dem Python-Werkzeug (disutils) zum Bauen und Installieren von Python-Modulen mit, dass es eine Erweiterungsdatei erzeugen soll. Mit dem Befehl --inplace entsteht die Erweiterungsdatei im aktuellen Verzeichnis und das Erweiterungsmodul lässt sich anschließend verwenden.

Verwenden des Erweiterungsmoduls (Abb. 4)

Abbildung 4 zeigt, dass sich das Erweiterungsmodul importieren lässt und die Funktion helloWorld aufrufbar ist. Der Befehl dir(helloWorld) stellt die Attribute des Moduls helloWorld dar. Das SWIG-Beispiel war zugegebenermaßen sehr einfach gestrickt. Dafür ist das folgende Beispiel deutlich komplexer: In ihm kommen zum ersten Mal in dieser Artikelserie C++ und pybind11 zum Einsatz.