Ergebnis 1 bis 9 von 9

Thema: [TUT] SDK: Wie wrappe ich eine Funktion?

  1. #1
    verschollen Avatar von deepwater
    Registriert seit
    23.04.11
    Beiträge
    1.582

    [TUT] SDK: Wie wrappe ich eine Funktion?

    Nächste Runde.
    Grund: Mir ist langweilig.

    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.

    1. Wie funktioniert der Funktionsaufruf über Python?
    2. Neue Funktion einer vorhandenen Klasse
    3. 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:

    Bild





    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.
    PHP-Code:
       .def("verstecken", &CyUnit::verstecken"bool ()"
    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.
    Achtung Spoiler:
    Header:
    PHP-Code:
    //CvMeineKlasse.h

    #ifndef CV_MEINE_KLASSE_H_
    #define CV_MEINE_KLASSE_H_

    class CvMeineKlasse
    {
    public:
        
    bool verstecken();    //Testfunktion
        
    bool Aufruf();
    };
    #endif 
    Quellcode:
    PHP-Code:
    //CvMeineKlasse.cpp

    #include "CvGameCoreDLL.h"
    #include "CvMeineKlasse.h"
    #include "CyMeineKlasse.h"
    #include "CyArgsList.h"

    bool CvMeineKlasse::verstecken()
    {
        return 
    true;
    }

    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.
        
    CyMeineKlasseCyInst = 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"

    #ifndef CY_MEINE_KLASSE_H_
    #define CY_MEINE_KLASSE_H_

    class CyMeineKlasse
    {
    public:
        
    CyMeineKlasse(); //default constructor
        
    CyMeineKlasse(CvMeineKlassepCvMeineKlasse); //unser Konstruktor
        
    bool verstecken();

    protected:
        
    CvMeineKlassem_pMeineKlasse//Zeiger auf die eigentliche
                                       //Klasseninstanz
    };
    #endif 
    und "CyMeineKlasse.cpp":
    PHP-Code:
    //CyMeineKlasse.cpp

    #include "CvGameCoreDLL.h"
    #include "CyMeineKlasse.h"

    //Der Konstruktor
    CyMeineKlasse::CyMeineKlasse(CvMeineKlassepCvMeineKlasse) : 

    m_pMeineKlasse(pCvMeineKlasse)    //füllt unseren Zeiger auf die 
                                      //"eigentliche" Klasse
    {
    }

    //Für CvDLLPython brauchen wir noch einen "default constructor"!
    CyMeineKlasse::CyMeineKlasse()
    {
    }

    bool CyMeineKlasse::verstecken() //unsere Funktion
    {
        return (
    m_pMeineKlasse m_pMeineKlasse->verstecken() : false);

    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.
    PHP-Code:
    //CyMeineKlasseInterface.cpp

    #include "CvGameCoreDLL.h"
    #include "CyMeineKlasse.h"

    //
    // published python interface for CyMeineKlasse
    //

    void CyMeineKlassePythonInterface()
    {
        
    python::class_<CyMeineKlasse>("CyMeineKlasse")
            .
    def("verstecken", &CyMeineKlasse::verstecken"bool ()")
        ;

    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.
    PHP-Code:
    //neu
    #include "CyMeineKlasse.h"

    void CyMeineKlassePythonInterface();
    (...)
    DLLExport void DLLPublishToPython()
    {
    //neu
        
    CyMeineKlassePythonInterface();
    (...) 
    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!
    Angehängte Grafiken Angehängte Grafiken
    Geändert von deepwater (05. September 2011 um 21:14 Uhr)

    ...too old...

  2. #2
    Waddehaddedudeda Avatar von Cybah
    Registriert seit
    01.09.06
    Beiträge
    30.060
    da oben steht nix im spoiler.
    Pucc's Lets Plays BASE 6.0: #1 #2 #3 #4 #5

    Download von BASE 6.4: HIER (klick mich!) (Stand: 12.07.2019)

  3. #3
    Registrierter Benutzer Avatar von alpha civ
    Registriert seit
    22.07.06
    Beiträge
    16.630
    Da wird das Bild nicht angezeigt.

  4. #4
    verschollen Avatar von deepwater
    Registriert seit
    23.04.11
    Beiträge
    1.582
    Ach mann, aber das Problem gibts scheinbar öfter.
    Mal gucken, ob sich da was machen lässt. Ich kanns sehen.

    Edit: Welches funktioniert?

    ...too old...

  5. #5
    Registrierter Benutzer Avatar von alpha civ
    Registriert seit
    22.07.06
    Beiträge
    16.630
    Jetzt kann man beide sehen.

  6. #6
    verschollen Avatar von deepwater
    Registriert seit
    23.04.11
    Beiträge
    1.582
    Aha, funktionierts jetzt?

    ...too old...

  7. #7
    Registrierter Benutzer Avatar von alpha civ
    Registriert seit
    22.07.06
    Beiträge
    16.630
    Ja.

  8. #8
    verschollen Avatar von deepwater
    Registriert seit
    23.04.11
    Beiträge
    1.582
    Bitte sehr, der zweite Teil, für die, die ihre eigenen Klassen brauchen.

    ...too old...

  9. #9
    Bereichsmoderator Avatar von Monaldinio
    Registriert seit
    10.11.09
    Ort
    HRO
    Beiträge
    7.634
    Conflict on Chiron - Sid Meier's Alpha Centauri vs. Call to Power!

    Neu Version Conflict on Chiron v3.4 BETA - 16.01.16

    Patch1 - 07.04.16

    Die deutschen Sounds und Wunderfilme sind bereits in der MainFile integriert!
    Ihr könnt sofort loslegen.

    Über Feedback würde ich mich freuen...

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •