Zunächst: Was ist das "Wrappen"?
Nun, mit wrappen wird bezeichnet, eine Funktion aus dem SDK (also C++) für den Pythonteil verfügbar zu machen. Nach erfolgreichem Abschluss dieses Tutorials solltet ihr eure Funktion also über Python (z.B. CvEventManager.py) aufrufen können.
Wenn ihr neue Funktionen erstellt ist es manchmal ratsam sie zu wrappen, eure Modmodder werden euch dankbar sein nicht das halbe SDK umkrempeln zu müssen.
Wie funktioniert der Funktionsaufruf über Python?
Neue Funktion einer vorhandenen Klasse
Neue Funktion einer eigenen Klasse
Wie funktioniert der Funktionsaufruf über Python?
Nehmen wir dazu ein Beispiel: Mit der Funktion getOwner() kann man von einer Einheit die BesitzerID erfahren. Bauen wir das jetzt mal in den CvEventManager ein, dann steht da sowas wie:
PHP-Code:
iBesitzer = pUnit.getOwner() ##Der Zeiger pUnit muss natürlich vorher definiert
##sein. Er muss auf eine CyUnit Instanz zeigen.
Python liegt in Skripten vor, das SDK kommt jetzt daher und liest diese Skripte. Dabei findet es auch unseren Funktionsaufruf. Anhand des Zeigers pUnit, welcher auf eine Instanz der CyUnit-Klasse zeigt, wird nun die Funktion getOwner() aufgerufen. In Zeile 1465 von CyUnit.cpp ist diese Funktion definiert.
Dort wird jetzt die eigentliche Funktion der CvUnit-Klasse aufgerufen. Der Rückgabewert wird auf demselben Wege wieder zurückgereicht.
Achtung Spoiler:
1. Neue Funktion einer vorhandenen Klasse
Ausgangslage
Anhand eines Beispiels lässt sich immer gut erklären, sagen wir also, wir hätten eine neue Funktion für CvUnit geschrieben.
PHP-Code:
bool CvUnit::verstecken()
Die Funktion ermöglicht es der Einheit, wenn sie auf einem Wald steht, sich zu verstecken und so unsichtbar zu sein, wenns funktioniert hat, dann liefert die Funktion 1 also true zurück, ansonsten, z.B. wenn die Einheit nicht auf einem Wald steht 0 also false. Sie funktioniert auch wunderbar. Jetzt stellen wir aber fest, dass wir die Funktion dringend im CvEventManager.py brauchen. Also wrappen wir sie und zwar folgendermaßen.
CyUnit-Klasse
Da die Funktion ein Member der CvUnit-Klasse ist gibt es schon ein Pythoninterface und wir müssen nur ein paar Zeilen in den ensprechenden Dateien hinzufügen.
Zuerst müssen wir die Funktion in der CyUnit-Klasse definieren.
Dafür öffnen wir zuerst die Dateien CyUnit.h und CyUnit.cpp.
Zuerst deklarieren wir die Funktion in CyUnit.h. Wo ist dabei ziemlich egal, solange sie im Publicbereich der CyUnit-Klasse bleibt.
PHP-Code:
bool verstecken();
Nun schauen wir uns mal die CyUnit.cpp Datei an. In ihr sind sämtliche CyUnit Funktionen definiert.
Hier ist es völlig egal wo wir unsere Funktion definieren, solange wir es nicht in einer anderen Funktion machen. Der Platz ganz am Ende sieht doch gut aus, oder? Da es sich lediglich um einen Aufruf der eigentlichen Funktion handelt sind es nur ein paar Zeilen.
PHP-Code:
bool CyUnit::verstecken()
{
return (m_pUnit/*Das ist der Zeiger auf die eigentliche Einheit.*/->verstecken());
}
Jetzt hätten wir unsere Funktion schonmal der CyUnit-Klasse hinzugefügt, aber in Python können wir immer noch nicht auf sie zugreifen.
Pythoninterface
Also lasst uns das schnell ändern.
Nun brauche wir die CyUnitInterface1.cpp Datei.
Dort sind alle Unit Funktionen die in Python aufgerufen werden und zwar nach diesem Schema:
PHP-Code:
.def("Funktionsname", &Zeiger auf die Funktion, "Rückgabetyp (Paramterlist) - Eventuelle Erläuterungen")
Also füge wir nun unser eigenes .def hinzu. Wo ist mal wieder relativ egal, solange es nach dem x und vor dem Semikolon ( ; ) ist.
Jetzt müssen wir die CvGameCoreDLL nur neu kompilieren und zu unserer Mod hinzufügen, und schon können wir z.B im CvEventManager darauf zugreifen.
2. Neue Funktionen einer eigenen Klasse
Weil mir der ober Teil nicht challenging genug war, hier etwas fortgeschrittener.
Wir haben wieder eine Funktion erschaffen, diesmal allerdings in einer eigenen Klasse, geben wir dieser Klasse den äußerst coolen Namen "Meine Klasse".
Sie hat eine eigene Header- und Quelldatei. (CvMeineKlasse.h bzw. CvMeineKlasse.cpp)
Nun wollen wir wieder die Funktion verstecken() wrappen.
Zum besseren Verständnis die kurze CvMeineKlasse-Klasse.
bool CvMeineKlasse::Aufruf()
{
// So könnte ein Pythonaufruf aussehen. Wichtig ist dabei die "makePython-
//Object" Funktion. Über sie wird ein "Pythonobjekt unserer Wrapperklasse
//erstellt, das wir dann an die Pythonmethode weiterreichen können.
CyMeineKlasse* CyInst = new CyMeineKlasse(this);
CyArgsList argsList;
argsList.add(gDLL->getPythonIFace()->makePythonObject(CyInst));
long lResult=0;
gDLL->getPythonIFace()->callFunction(PYGameModule, "isTest", argsList.makeFunctionArgs(), &lResult);
SAFE_DELETE(CyInst); // python fxn must not hold on to this pointer
if (lResult == 1)
{
return true;
}
return false;
}
Wie man sieht, hat die Klasse unsere Pseudofunktion "verstecken()", die wir wrappen werden, und eine Funktion "Aufruf()". Über diese Funktion wird ein Pythonaufruf an eine Pythonmethode gemacht.
Die eigentlich Funktion verstecken liefert nur ein true zurück, nicht sehr spektakulär.
CyMeineKlasse
Zuerst müssen wir zwei Dateien erstellen. CyMeineKlasse.h und CyMeineKlasse.cpp. (Nicht vergessen sie eurem Projekt hinzuzufügen, sonst wird da nämlich gar nichts kompiliert.) Zuerst "CyMeineKlasse.h":
PHP-Code:
//CyMeineKlasse.h
//Python Wrapperclass for "CvMeineKlasse"
#pragma once
#include "CvMeineKlasse.h"
Das ist eine einfache Klasse, deren Memberfunktionen nur Aufrufe an die eigentliche CvMeineKlasse sind. Wie wir sehen wird "CvGameCoreDLL.h" eingebunden, dies muss in jeder cpp-Datei geschehen, da für das Projekt vorkompilierte Headerdirektiven benutzt werden, welchen den Kompilierungsvorgang beschleunigen. Dann muss natürlich in der cpp-Datei die eigene Headerdatei eingebunden werden. Die Headerdatei der CvMeineKl. wird ja schon im Header der Klasse CyMeineKl. eingebunden, deshalb brauchen wir es nicht nochmal in der Quelldatei machen.
Zu den Memberfunktionen:
Der "default constructor" wird benötigt, um das Pythoninterface zu initialisieren, dabei wird nämlich eine Instanz dieser Klasse erstellt. Allerdings wäre damit die Klasse ziemlich nutzlos. Da sie ja nicht wüsste, welcher Instanz der eigentlichen Klasse sie zugehörig ist. Deshalb brauchen wir noch unseren eigenen Konstruktor.
Der "eigene Konstruktor" definiert die einzige Membervariable dieser Klasse, m_pMeineKlasse. Das ist ein Zeiger auf die Instanz der CvMeineKl., der diese Instanz zugeordnet ist.
Unsere zu wrappende Funktion "verstecken". Sie ist ein Aufruf an die eigentlich Funktion, sofern der Zeiger nicht null ist.
CyMeineKlasseInterface
Nun brauchen wir aber noch das eigentlich Interface.
Wieder das obligatorische CvGameCoreDLL.h include, und natürlich die Wrapperklasse.
Die einzige Funktion dieser Datei ist "void CyMeineKlasseInterface()". Sie wird beim Starten des Spiels, bie init Python, aufgerufen. Dann wird die Klasse (CyMeineKlasse) Python sozusagen "bekannt gemacht". Und dann kommt unsere eigene Funktion, erst der Name der Methode, über welchen sie dann also aufgerufen wird, dann ein Zeiger auf die Funktion der Wrapperklasse, und dann der Rückgabetyp samt Parameterliste. Und am Schluss das Semikolon nicht vergessen.
Sollte eure Klasse so groß sein, dass ihr nicht mit einer Datei auskommt, dann orientiert euch einfach mal an einer Klasse die ein so großes Pythoninterface hat, dass es mehrere Dateien füllt. (zum Besipiel "CyCity")
Und auch noch sehr wichtig zum Schluss:
CvDLLPython
In dieser Datei werden die ganzen Pythoninterfaces initialisiert, oder selbiges zumindestens veranlasst.
Zunächst muss unsere Wrapperklasse eingebunden werden.
Dann kommt ein Aufruf an unser Interface dieser muss exakt mit der Funktion in userer Interfacedatei übereinstimmen.
Dasselbe nochmal in der Funktion "DLLPublishtToPython()". Letztgenannte Funktion wird direkt aus der exe aufgerufen und veranlasst, dass die Pythoninterfaces initialisiert werden.
Wenn jetzt alles geklappt hat, dann müsstet ihr eure Funktin über Python aufrufen können, vergesst aber nicht, dass ihr ein PyObjekt vorher der Pythonmethode mitgegeben haben müsst!
Geändert von deepwater (05. September 2011 um 21:14 Uhr)