Wrapping a Cplusplus class in Python/de

Dieser Artikel ist ein Stummel. Bitte tragt Euer Wissen dazu bei!

Hintergrund

FreeCAD verwendet ein benutzerdefiniertes XML-basiertes System, um den Python-Wrapper für eine C++-Klasse zu erstellen. Um eine C++-Klasse für die Verwendung in Python zu verpacken, müssen zwei Dateien manuell erstellt werden, und zwei Dateien werden automatisch vom CMake-Build-System generiert (zusätzlich zu den C++-Header- und Implementierungsdateien für die Klasse).

Es muss erstellt werden:

Man bearbeitet die entsprechende Datei CMakeLists.txt, um Verweise auf diese beiden Dateien hinzuzufügen. Aus der XML-Datei erstellt das Build-System dann Folgendes:

XML-Datei mit Klassenbeschreibung

Die XML-Datei [YourClass]Py.xml enthält Informationen zu den Funktionen und Attributen, die die Python-Klasse implementiert, sowie die Benutzerdokumentation für diese Elemente, die in FreeCAD in der Python-Konsole anzeigt.

Für dieses Beispiel schauen wir uns den Wrapper der Achsen-C++-Klasse an. Die XML-Beschreibung beginnt mit:

<?xml version="1.0" encoding="UTF-8"?>
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
    <PythonExport
        Father="PyObjectBase"
        Name="AxisPy"
        Twin="Axis"
        TwinPointer="Axis"
        Include="Base/Axis.h"
        FatherInclude="Base/PyObjectBase.h"
        Namespace="Base"
        Constructor="true"
        Delete="true"
        FatherNamespace="Base">
    <Documentation>
        <Author Licence="LGPL" Name="Juergen Riegel" EMail="FreeCAD@juergen-riegel.net" />
        <UserDocu>User documentation here
        </UserDocu>
        <DeveloperDocu>Developer documentation here</DeveloperDocu>
    </Documentation>

Im Anschluss an diese Einleitung folgt eine Liste von Methoden und Attributen. Das Format einer Methode lautet:

<Methode Name="move">
    <Documentation>
        <UserDocu>
        move(Vector)
        Move the axis base along the vector
        </UserDocu>
    </Documentation>
</Methode>

Das Format eines Attributes ist:

<Attribute Name="Direction" ReadOnly="false">
    <Documentation>
        <UserDocu>Direction vector of the Axis</UserDocu>
    </Documentation>
    <Parameter Name="Direction" Type="Object" />
</Attribute>

Wenn für ein Attribut „ReadOnly” falsch ist, muss man sowohl eine Getter- als auch eine Setter-Funktion bereitstellen. Ist es wahr, ist nur eine Getter-Funktion zulässig. In diesem Fall müssen wir zwei Funktionen in der C++-Implementierungsdatei bereitstellen:

Py::Object AxisPy::getDirection(void) const

und:

void AxisPy::setDirection(Py::Object arg)

Implementierungs-Cplusplus-Datei

Die Implementierungsdatei [YourClass]PyImp.cpp in C++ fungiert als "Klebstoff", der die C++- und Python-Strukturen miteinander verbindet und so eine effektive Übersetzung von einer Sprache in die andere ermöglicht. Das FreeCAD-System für die Übersetzung von C++ nach Python stellt eine Reihe von C++-Klassen bereit, die ihren entsprechenden Python-Typen zugeordnet sind. Die grundlegendste davon ist die Klasse Py::Object – diese Klasse wird selten direkt erstellt, bildet jedoch die Basis des Vererbungsbaums und wird als Rückgabetyp für alle Funktionen verwendet, die Python-Daten zurückgeben.

Dateien einbinden

Die C++_Implementationsdatei enthält die folgenden Dateien:

#include "PreCompiled.h"

#include "[YourClass].h"

// Inclusion of the generated files (generated out of [YourClass]Py.xml)
#include "[YourClass]Py.h"
#include "[YourClass]Py.cpp"

Selbstverständlich kann man auch alle anderen C++-Header einbinden, die der Code benötigt, um zu funktionieren.

Konstruktor

Die C++-Implementierung muss die Definition der PyInit-Funktion enthalten: Für den Axis-Klassen-Wrapper lautet diese beispielsweise

int AxisPy::PyInit(PyObject* args, PyObject* /*kwd*/)

Innerhalb dieser Funktion muss man höchstwahrscheinlich eingehende Argumente für den Konstruktor analysieren: Die wichtigste Funktion für diesen Zweck ist die von Python bereitgestellte PyArg_ParseTuple. Sie nimmt die übergebene Argumentliste, einen Deskriptor für die zu analysierenden erwarteten Argumente sowie Typinformationen und Speicherorte für die analysierten Ergebnisse entgegen. Beispiel:

PyObject* d;
    if (PyArg_ParseTuple(args, "O!O", &(Base::VectorPy::Type), &o,
                                      &(Base::VectorPy::Type), &d)) {
        // NOTE: The first parameter defines the base (origin) and the second the direction.
        *getAxisPtr() = Base::Axis(static_cast<Base::VectorPy*>(o)->value(),
                                   static_cast<Base::VectorPy*>(d)->value());
        return 0;
    }

Eine vollständige Liste der Formatbezeichner findet man in der Python C API-Dokumentation. Man beachte, dass auch mehrere verwandte Funktionen definiert sind, die die Verwendung von Schlüsselwörtern usw. ermöglichen. Der vollständige Satz lautet:

PyAPI_FUNC(int) PyArg_Parse (PyObject *, const char *, ...);
PyAPI_FUNC(int) PyArg_ParseTuple (PyObject *, const char *, ...);
PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords (PyObject *, PyObject *, const char *, char **, ...);
PyAPI_FUNC(int) PyArg_VaParse (PyObject *, const char *, va_list);
PyAPI_FUNC(int) PyArg_VaParseTupleAndKeywords (PyObject *, PyObject *, const char *, char **, va_list);

Verweise