C++ DLL in Python verwenden
In diesem Tutorial zeige ich wie ich eine in C++ erstellte DLL Bibliothek in einem Python Programm verwende. Mit ein wenig Aufwand kann man bereits fertige Bibliotheken in beliebigen Programmiersprachen nutzen und spart jede Menge Zeit.
C++ DLL in Python verwenden
Kontext
Bevor ich näher auf das Setup eingehe kurz der Kontext. Ich verwende eine selbst geschriebene C++ Bibliothek (HexagonTileMap) für ein Unreal Engine Projekt (deshalb C++). Um mir KI Agenten nicht selbst programmieren zu müssen möchte ich diese mit Deep Reinforcement Learning in PyTorch trainieren. Um die dafür nötige Umgebung nicht neu zu entwickeln möchte ich die C++ DLL in Python nutzen.
Pybind
Ich verwende in meinem Demoprojekt Pybind, da der Aufwand ein bestehendes C++ Projekt für Python nutzbar zu machen denkbar gering ist. Ich verwende die aktuelle Version 3.12.2 von Python. Mit dem Befehl
pip install pybind11
wird die aktuelle Version von Pybind installiert:
In meinem C++ Projekt lege ich nun neben Debug und Release noch eine eigene Konfiguration Pybind an, damit wird statt einer DLL eine *.pyd Datei gebaut.
Unter Advanced ändert man nun die Target File Extension von DLL auf PYD. Damit wird nun eine *.pyd Datei anstatt einer *.dll erstellt.
Pybind benötigt einige Zeilen Konfiguration pro Klasse bzw. Methode, damit diese korrekt unter Python ansprechbar ist. Damit diese kompiliert werden kann benötigt man Pybind Header und die Python *.lib Datei. Die Pfade dafür füge ich nun der Konfiguration hinzu. Der folgende Befehl
python -m pybind11 --includes
gibt die Pfade für die Pybind includes aus, diese müssen unter C/C++ General Additional Include Directories hinzugefügt werden:
Wichtig: unter Windows Python bitte nicht über den Windows Store installieren, sondern über den Installer der Python Webseite. Ansonsten kann es sein, dass die Includes oder Libs aufgrund von fehlenden Zugriffsrechten von Visual Studio nicht verwendet werden können.
Unter Linker General – Additional Library Directories füge ich nun auch noch den Pfad zur aktuellen python.lib hinzu.
Pybind Macro
Das PYBIND11_MODULE macro definiert den Einstiegspunkt für Python und definiert die verwendbaren Objekte. In meinem Beispiel ist das die Hex Klasse, die Koordinaten für eine Hexagon Map definieren. Die ganze noch nötige Arbeit für den Entwickler ist nun alle für Python sichtbaren Objekte korrekt zu beschreiben. Das kann beispielsweise so aussehen (zu finden in der Hex.cpp Datei):
#ifdef PYBIND #include <pybind11/pybind11.h> #include <pybind11/stl.h> namespace py = pybind11; PYBIND11_MODULE(HexagonTileMap, m) { py::class_(m, "Hex") .def(py::init<int, int, int>(), py::arg("q"), py::arg("r"), py::arg("s")) .def("q", &Hex::q) .def("r", &Hex::r) .def("s", &Hex::s) .def("length", &Hex::length) .def("distance", &Hex::distance) .def("neighbor", &Hex::neighbor) .def("__eq__", &Hex::operator==) .def("__ne__", &Hex::operator!=) .def("__add__", &Hex::operator+) .def("__sub__", &Hex::operator-) .def("__mul__", &Hex::operator*, py::arg("k")); #ifdef VERSION_INFO m.attr("__version__") = VERSION_INFO; #else m.attr("__version__") = "dev"; #endif } #endif
Bauen
Mit den Einstellungen der Pybind Konfiguration baut Visual Studio nun wie gewünscht die *.pyd Datei.
Alternative
Eine Alternative zu Pybind11 ist beispielsweise SWIG. Das war initial auch mein erster Versuch für diesen Artikel, ich habe mich aber dann dagegen entschieden, weil der Boilerplate Code den man schreiben muss damit es funktioniert wesentlich mehr ist. Gerade bei der Verwendung mit Visual Studio ist SWIG meiner Meinung nach eher ein Nachteil.
Fazit
Ich habe einen Weg gezeigt mit möglichst wenig Aufwand einen bestehenden C/C++ Code unter Python zu benutzen. Pybind baut eine für Python lesbare Bibliotheksdatei und erlaubt die Verwendung des optimierten Codes. Anwendung findet solch ein Auslagern von Berechnungen nach C++ immer dann, wenn diese schnell und optimiert sein soll. Python ist dafür manchmal zu langsam.