C- und C++-Erweiterungen unter Windows bauen

Dieses Kapitel erläutert kurz, wie du mit Microsoft Visual C++ ein Windows-Erweiterungsmodul für Python erstellst, gefolgt von ausführlicheren Hintergrundinformationen zur Funktionsweise. Die Erläuterungen sind sowohl für Windows-Programmierer nützlich, die lernen möchten, Python-Erweiterungen zu bauen, als auch für Unix-Programmierer, die Software entwickeln wollen, die sich sowohl unter Unix als auch unter Windows erfolgreich bauen lässt.

Modulautoren werden dazu angehalten, beim Erstellen von Erweiterungsmodulen den distutils-Ansatz zu verwenden statt des in diesem Abschnitt beschriebenen. Du brauchst weiterhin den C-Compiler, mit dem Python gebaut wurde, in der Regel Microsoft Visual C++.

Bemerkung

In diesem Kapitel werden mehrere Dateinamen erwähnt, die eine kodierte Python-Versionsnummer enthalten. Diese Dateinamen werden mit der Versionsnummer in der Form XY dargestellt. In der Praxis entspricht 'X' der Hauptversionsnummer und 'Y' der Nebenversionsnummer der Python-Ausgabe, mit der du arbeitest. Verwendest du zum Beispiel Python 2.2.1, lautet XY tatsächlich 22.

Ein Kochbuch-Ansatz

Es gibt zwei Ansätze, Erweiterungsmodule unter Windows zu bauen, genau wie unter Unix: Entweder nutzt du das Paket setuptools, um den Bauvorgang zu steuern, oder du führst die Schritte von Hand aus. Der setuptools-Ansatz funktioniert bei den meisten Erweiterungen gut. Eine Dokumentation dazu, wie sich mit setuptools Erweiterungsmodule bauen und paketieren lassen, steht unter C- und C++-Erweiterungen mit setuptools bauen. Stellst du fest, dass du die Schritte wirklich von Hand ausführen musst, kann es hilfreich sein, die Projektdatei des Standardbibliotheksmoduls winsound zu studieren.

Unterschiede zwischen Unix und Windows

Unix und Windows verwenden völlig unterschiedliche Vorgehensweisen, um Code zur Laufzeit zu laden. Bevor du versuchst, ein Modul zu bauen, das sich dynamisch laden lässt, solltest du wissen, wie dein System arbeitet.

Unter Unix enthält eine Shared-Object-Datei (.so) Code, den das Programm verwenden soll, sowie die Namen der Funktionen und Daten, die sie im Programm erwartet. Wird die Datei mit dem Programm verbunden, werden alle Verweise auf diese Funktionen und Daten im Code der Datei so geändert, dass sie auf die tatsächlichen Stellen im Programm zeigen, an denen die Funktionen und Daten im Speicher liegen. Im Grunde ist das ein Verknüpfungsvorgang.

Unter Windows enthält eine Dynamic-Link-Library-Datei (.dll) keine hängenden Referenzen. Stattdessen erfolgt der Zugriff auf Funktionen oder Daten über eine Nachschlagetabelle. Der DLL-Code muss deshalb zur Laufzeit nicht angepasst werden, um auf den Speicher des Programms zu verweisen. Stattdessen nutzt der Code bereits die Nachschlagetabelle der DLL, und diese wird zur Laufzeit so geändert, dass sie auf die Funktionen und Daten verweist.

Unter Unix gibt es nur einen Typ von Bibliotheksdatei (.a), die Code aus mehreren Objektdateien (.o) enthält. Während des Verknüpfungsschritts zur Erstellung einer gemeinsam genutzten Objektdatei (.so) kann es vorkommen, dass der Linker nicht weiß, wo ein Bezeichner festgelegt ist. Der Linker sucht dann in den Objektdateien der Bibliotheken danach. Findet er ihn, bindet er den gesamten Code aus dieser Objektdatei ein.

Unter Windows gibt es zwei Arten von Bibliotheken: statische Bibliotheken und Importbibliotheken (beide heißen .lib). Eine statische Bibliothek entspricht einer .a-Datei unter Unix, sie enthält Code, der bei Bedarf eingebunden wird. Eine Importbibliothek dient im Wesentlichen nur dazu, dem Linker zu versichern, dass ein bestimmter Bezeichner zulässig ist und im Programm vorhanden sein wird, wenn die DLL geladen wird. Der Linker nutzt also die Angaben aus der Importbibliothek, um die Nachschlagetabelle für Bezeichner zu bauen, die nicht in der DLL enthalten sind. Wird eine Anwendung oder eine DLL verknüpft, kann dabei eine Importbibliothek entstehen, die für alle künftigen DLLs verwendet werden muss, die von den Symbolen der Anwendung oder DLL abhängen.

Angenommen, du baust zwei dynamisch geladene Module B und C, die sich einen weiteren Codeblock A teilen sollen. Unter Unix würdest du A.a nicht an den Linker für B.so und C.so übergeben. Das würde dazu führen, dass der Code zweimal eingebunden wird, sodass B und C jeweils eine eigene Kopie hätten. Unter Windows entsteht beim Bauen von A.dll auch A.lib. Diese A.lib übergibst du sehr wohl an den Linker für B und C. A.lib enthält keinen Code, sondern lediglich Angaben, über die zur Laufzeit auf den Code von A zugegriffen wird.

Eine Importbibliothek verhält sich unter Windows ähnlich wie import spam: Du bekommst Zugriff auf die Namen von spam, es entsteht aber keine eigene Kopie. Unter Unix ähnelt das Verknüpfen mit einer Bibliothek eher from spam import *, dabei entsteht eine eigene Kopie.

Deaktiviere die implizite, auf #pragma basierende Verknüpfung mit der Python-Bibliothek, die in den CPython-Header-Dateien erfolgt.

Added in version 3.14.

Der Einsatz von DLLs in der Praxis

Windows-Python wird mit Microsoft Visual C++ gebaut. Andere Compiler funktionieren möglicherweise, möglicherweise aber auch nicht. Der Rest dieses Abschnitts bezieht sich auf MSVC++.

Beim Erstellen von DLLs unter Windows kannst du die CPython-Bibliothek auf zwei Arten nutzen:

  1. Standardmäßig löst die Einbindung von PC/pyconfig.h – entweder direkt oder über Python.h – eine implizite, von der Konfiguration abhängige Verknüpfung mit der Bibliothek aus. Die Header-Datei wählt pythonXY_d.lib für die Debug-Version, pythonXY.lib für die Release-Version und pythonX.lib für die Release-Version mit aktivierter Limited API.

    Um zwei DLLs zu bauen, spam und ni (das C-Funktionen aus spam verwendet), könntest du diese Befehle verwenden:

    cl /LD /I/python/include spam.c
    cl /LD /I/python/include ni.c spam.lib
    

    Der erste Befehl hat drei Dateien erstellt: spam.obj, spam.dll und spam.lib. Spam.dll enthält zwar keine Python-Funktionen (wie beispielsweise PyArg_ParseTuple()), weiß aber dank des implizit verknüpften pythonXY.lib, wo der Python-Code zu finden ist.

    Der zweite Befehl hat ni.dll erzeugt (sowie .obj und .lib), die weiß, wie die nötigen Funktionen aus spam und auch aus der ausführbaren Python-Datei zu finden sind.

  2. Manuell, indem du das Makro Py_NO_LINK_LIB definierst, bevor du Python.h einbindest. Du musst pythonXY.lib an den Linker übergeben.

    Um zwei DLLs zu bauen, spam und ni (das C-Funktionen aus spam verwendet), könntest du diese Befehle verwenden:

    cl /LD /DPy_NO_LINK_LIB /I/python/include spam.c ../libs/pythonXY.lib
    cl /LD /DPy_NO_LINK_LIB /I/python/include ni.c spam.lib ../libs/pythonXY.lib
    

    Der erste Befehl hat drei Dateien erstellt: spam.obj, spam.dll und spam.lib. Spam.dll enthält zwar keine Python-Funktionen (wie beispielsweise PyArg_ParseTuple()), weiß aber dank pythonXY.lib, wo der Python-Code zu finden ist.

    Der zweite Befehl hat ni.dll erzeugt (sowie .obj und .lib), die weiß, wie die nötigen Funktionen aus spam und auch aus der ausführbaren Python-Datei zu finden sind.

Nicht jeder Bezeichner wird in die Nachschlagetabelle exportiert. Willst du, dass andere Module (einschließlich Python) deine Bezeichner sehen können, musst du _declspec(dllexport) angeben, etwa in void _declspec(dllexport) initspam(void) oder PyObject _declspec(dllexport) *NiGetSpamData(void).

Developer Studio bindet eine Vielzahl von Importbibliotheken ein, die du eigentlich nicht brauchst, und bläht deine ausführbare Datei damit um etwa 100 KB auf. Um sie loszuwerden, gib im Dialog „Project Settings“ auf der Registerkarte „Link“ ignore default libraries an. Füge die passende msvcrtxx.lib zur Liste der Bibliotheken hinzu.