JLI Spieleprogrammierung Foren-Übersicht JLI Spieleprogrammierung

 
 FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   BenutzergruppenBenutzergruppen 
 medals.php?sid=ca93460b9437591a408bc3ee704f9c67Medaillen   RegistrierenRegistrieren   ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin 

Funktion ruft beliebige Funktion auf
Gehe zu Seite 1, 2  Weiter
 
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Entwicklung
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
Maxim
Senior JLI'ler



Anmeldedatum: 28.03.2004
Beiträge: 249

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 11:15    Titel: Funktion ruft beliebige Funktion auf Antworten mit Zitat

Hallo!

Ich versuche eine Funktion zu schreiben die eine beliebige Funktion aufruft, wenn folgende Informationen gegeben sind:
Zeiger auf die Funktion
Alle Argumente (und dazugehörige Datentypen)
Der Rückgabedatentyp der Funktion
Aufrufart = _stdcall

Ich hab schon vieles versucht, aber ich komme nicht weiter. Ich vermute, dass das Problem nur mit Assembler zu lösen ist.
Irgendwelche Vorschläge?

Mfg Maxim
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Dragon
Super JLI'ler


Alter: 38
Anmeldedatum: 24.05.2004
Beiträge: 340
Wohnort: Sachsen
Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 12:07    Titel: Re: Funktion ruft beliebige Funktion auf Antworten mit Zitat

Maxim hat Folgendes geschrieben:
Ich hab schon vieles versucht, aber ich komme nicht weiter. Ich vermute, dass das Problem nur mit Assembler zu lösen ist.
Irgendwelche Vorschläge?

Mfg Maxim

Hmm, ich hab mal bei Google "Function Pointer" eingeben und hab folgendes Tutorial gefunden. Es ist zwar in englisch aber sollte etwas für dich sein.

http://www.newty.de/fpt/index.html

das ist aber auch nicht schlecht, vieleicht etwas kurz:
http://publications.gbdirect.co.uk/c_book/chapter5/function_pointers.html

mfg Dragon
_________________
Nur wenn man ein Ziel sieht, kann man es auch treffen.
___________
Mein Leben, Freunde und die Spieleentwicklung


Zuletzt bearbeitet von Dragon am 13.05.2006, 12:12, insgesamt einmal bearbeitet
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
Kampfhund
Super JLI'ler


Alter: 42
Anmeldedatum: 20.07.2002
Beiträge: 408

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 12:11    Titel: Antworten mit Zitat

Ich verstehe noch nicht ganz wie das im späteren programm aussehen soll.

Wenn du den Zeiger, sowie alle parameter des aufrufs hast, dann kannst du doch auch einfach:
CPP:
(*zeiger)(p1,p2,p3,...);


schreiben.
_________________
Kochen ist ein NP-schweres Optimierungsproblem.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
GreveN
JLI Master


Alter: 38
Anmeldedatum: 08.01.2004
Beiträge: 901
Wohnort: Sachsen - Dresden
Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 12:17    Titel: Antworten mit Zitat

Ich find "richtige" Funktionszeiger eigentlich immer nicht so dolle, zumal ich der Meinung bin, dass man eigentlich fast die selbe Funktionalität auch mit Functors bekommt, mir ist zumindest kein Fall bekannt, wo die echten Funktionspointer Functors gegenüber einen Vorteil hätten.

Zudem finde ich, sind die noch ein Stück einfacher zu implementieren und ich finde es sehr schön, Funktionen auch als Objekte zu betrachten.

Ich weiß nicht inwieweit dir das jetzt was nutzt, aber eine Überlegung schadet sicher nicht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Yahoo Messenger MSN Messenger
Maxim
Senior JLI'ler



Anmeldedatum: 28.03.2004
Beiträge: 249

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 12:29    Titel: Antworten mit Zitat

Kampfhund hat Folgendes geschrieben:
Ich verstehe noch nicht ganz wie das im späteren programm aussehen soll.

Wenn du den Zeiger, sowie alle parameter des aufrufs hast, dann kannst du doch auch einfach:
CPP:
(*zeiger)(p1,p2,p3,...);


schreiben.


Ich habe vergessen zu sagen, das es ein void-zeiger ist, der nicht als eine funktion betrachtet wird, d.h. der compiler erlaubt so was nicht.

@Dragon
die tuts kenn ich schon
ich weiß wie zeiger funktionieren, aber trotzdem danke

@GreveN
könntest du bitte mehr ins detail gehen und "Functors"(kenn ich nicht) am beispiel erklären?
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Kampfhund
Super JLI'ler


Alter: 42
Anmeldedatum: 20.07.2002
Beiträge: 408

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 12:40    Titel: Antworten mit Zitat

Maxim hat Folgendes geschrieben:

Ich habe vergessen zu sagen, das es ein void-zeiger ist, der nicht als eine funktion betrachtet wird, d.h. der compiler erlaubt so was nicht.


Void zeiger sind gefährlich. Wenn ich mich recht erinnere (ich habe nämlich mal was ähnliches gemacht), dann klappt das mit dem void zeiger auch nicht immer. Ich glaube das war im zusammenhang mit Mehrfachvererbung. Allerdings benutzt du ja stdcall...

Weist du schon zur kompilierzeit, welche funktionen du per pointer aufrufen wirst?

Und wie willst du die funktionen aufrufen?

CPP:
void machwas(int x,int y){...}

FunktionsPointerStorage s;
s.Register("machwas",&machwas);

s.push_param(p1);
s.push_param(p2);
s.Call("machwas");


So in der Art?
_________________
Kochen ist ein NP-schweres Optimierungsproblem.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
Maxim
Senior JLI'ler



Anmeldedatum: 28.03.2004
Beiträge: 249

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 13:02    Titel: Antworten mit Zitat

Im groben....schon
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
GreveN
JLI Master


Alter: 38
Anmeldedatum: 08.01.2004
Beiträge: 901
Wohnort: Sachsen - Dresden
Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 13:05    Titel: Antworten mit Zitat

Also ein Functor ist im Grunde nichts weiter als eine Klasse/Struktur, mit einem überladenem ()-Operator (eine Methode "call" oder was auch immer, würde natürlich auch gehen), du kannst deiner aufrufenden Funktion dann ein Objekt der Klasse übergeben, und die Funktion ruft einfach diese Methode auf.

Beispiel für einen Funktor der 2 Zahlen addiert:
CPP:
struct add
{
    int operator()(int a, int b) { return a+b; }
};

...

add func;

int a = func(3, 5);


Das das in dieser Form natürlich Unsinn ist, dürfte klar sein. Das naheliegendste dürfte sein, das ganze erstmal zu templatisieren und weiterhin von einer Basisklasse abzuleiten, um schön flexibel zu bleiben. Das Beste ist, die Standardlib baut auf Functors auf und hat deshalb da schon etwas nettes Kleines implementiert, in deinem Fall könnte das z.B. so ausschauen:
CPP:
template<typename T>
void deine_funktion(const std::binary_function<T, T, T>& func);

...

template<typename T>
struct add : public std::binary_function<T, T, T>
{
    const T operator()(const T& a, const T& b) const { return (a+b); }
};

...

add<int> func;

deine_funktion(func);

std::wcout << func(1, 2) << std::endl;

Und so weiter, die Einsatzmöglichkeiten sind endlos...

Für Functors mit nur einem Parameter gibts übrigens 'std::unary_function', die 3 Templateparameter bei 'std::binary_function' dürften in ihrer Bedeutung auch klar sein: 1. Parameter, 2. Parameter, Rückgabetyp.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Yahoo Messenger MSN Messenger
Maxim
Senior JLI'ler



Anmeldedatum: 28.03.2004
Beiträge: 249

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 13:13    Titel: Antworten mit Zitat

verstehe
Die Anzahl der Parameter muss aber variabel sein.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
GreveN
JLI Master


Alter: 38
Anmeldedatum: 08.01.2004
Beiträge: 901
Wohnort: Sachsen - Dresden
Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 13:20    Titel: Antworten mit Zitat

Verdammt, hab' ich nicht mitgekriegt. Cool

Wenn das mit sprachinternen Mitteln geht, würde mich auch mal interessieren wie. Cool
Vielleicht irgendeinen Container übergeben, der die eigentlich Funktionsargumente irgendwie beinhaltet?

Achja, was willst du eigentlich machen, vielleicht gibts eine bessere Lösung?
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Yahoo Messenger MSN Messenger
Maxim
Senior JLI'ler



Anmeldedatum: 28.03.2004
Beiträge: 249

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 13:31    Titel: Antworten mit Zitat

eine kleine script-sprache(funktionen aufrufen und mit variablen rumspielen)
funktioniert schon wunderbar, aber nur für vorgegebene funktion
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Kampfhund
Super JLI'ler


Alter: 42
Anmeldedatum: 20.07.2002
Beiträge: 408

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 13:48    Titel: Antworten mit Zitat

Maxim hat Folgendes geschrieben:
eine kleine script-sprache(funktionen aufrufen und mit variablen rumspielen)
funktioniert schon wunderbar, aber nur für vorgegebene funktion


Daran arbeite ich auch gerade Smile

Ich kenne eine Lösung für das Problem. Es ist die, die auch Luabind verwendet (jedenfalls glaube ich das).

Ich schreib gleich mal was dazu.
_________________
Kochen ist ein NP-schweres Optimierungsproblem.


Zuletzt bearbeitet von Kampfhund am 13.05.2006, 15:13, insgesamt einmal bearbeitet
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
Kampfhund
Super JLI'ler


Alter: 42
Anmeldedatum: 20.07.2002
Beiträge: 408

Medaillen: Keine

BeitragVerfasst am: 13.05.2006, 15:10    Titel: Antworten mit Zitat

Ziel ist es, im Scriptinterpreter funktionen registrieren zu können, die dann aus der Scriptsprache heraus aufgerufen werden können.

Etwa so:
CPP:
native_class<test> native_test("test");

native_test
   .method("bar",&test::bar)
   .method("foo",&test::foo);


und im Skript:
CPP:
test t;
t.bar(12);


Da man die Funktionen per Hand registriert, ist bereits zur kompilierzeit klar, welche funktionen aufgerufen werden können.

Templates kann man daher gut dafür verwenden. Templates können hier nämlich viel Arbeit abnehmen.

CPP:
struct callable
{
   virtual void call()=0;
};

template<class M>
struct method_object : public callable
{
   virtual void call(){}
};

// spezialisierung für eine method mit keinem parameter
template<class R,class C>
struct method_object<R (C::*)()> : public callable
{

};

// spezialisierung für eine methode mit einem parameter
template<class R,class C,class P1> : public callable
struct method_object<R (C::*)(P1)> : public callable
{

};

...

// spezialisierungen für methoden mit n parametern
tempalte<class R,class C,class P1,...,class Pn>
struct method_object<R (C::*)(P1,...,Pn)> : public callable
{

};


(Diese mühsame Schreibarbeit kann man sich übrigends ersparen, indem man Makros verwendet. Sucht dazu einfach nach Stichworten "boost vertical and horizontal repetition".)

Was bringt uns dieses Template?
Ganz einfach: man kann für jeden Signaturtypen von Methoden spezifischen Code schreiben. Außerdem verfügen alle template-Klassen über die Schnittstelle callable.

Ich mach jetzt mal exemplarisch mit method_object<R (C::*)(P1)> weiter.

CPP:
//==================================================
template<class R,class C,class P1>
class method_object<R(C::*)(P1)> : public callable
{
   typedef R (C::*method_pointer_t)(P1);
public:

   method_object(method_pointer_t ptr) : method_pointer(ptr) {}

   virtual void call()
   {
      P1 p1=fetch_parameter<P1>();
      execute<R>(fetch_this(),p1);
   }

private:
   inline C* fetch_this()
   {
      C* this_ptr = new C; // get this from operand stack
      return this_ptr;
   }

   template<class P>
   inline P fetch_parameter()
   {
      return P(0); // get parameter vom operand stack
   }

   // called if R != void
   template<class RetType>
   inline void execute(C* instance,P1& param1)
   {
      stored_return_value.value = (*instance.*(method_pointer))(param1);
   }
   // called if R = void
   template<>
   inline void execute<void>(C* instance,P1& param1)
   {
      (*instance.*(method_pointer))(param1);
   }

   method_pointer_t   method_pointer;
   return_value<R>      stored_return_value;
};


Dieses Template enthält noch Dummy-Code. Man muss ihn entsprechen an den eigenen Interpreter anpassen.

Zur funktionsweise des Templates:
method_pointer_t ist ein typedef für den methoden pointer typ: R (C::*)(P1)
Im Constructor wird ein method_pointer_t übergeben und in der Membervariablen method_pointer gespeichert. Dabei wird er NICHT als void Zeiger gespeichert.

Die call methode ist implementiert, und zwar folgendermaßen:
Zuerst werden methoden des Templates aufgerufen, die die Parameter für den zeiger-methodenaufruf von irgendwoher holen (fetch_this, fetch_param). In diesem Beispiel sind diese Methode einfach dummys (fetch_this erstellt ein neues this-objekt und fetch_param erstellt einen mit 0 initialisierten parameter).

In der Richtigen implementierung hätte man dort sowas wie:
CPP:
template<class P>
inline P fetch_parameter()
{
   // Interpreter ist ein Singleton
   // FetchTopOperand ist eine template Methode die auch eine Typüberprüfung durchführt: Ist der Operand auf dem Operand Stack auch vom typ P?
   return Interpreter::Instance().GetOperandStack().FetchTopOperand<P>();
}


Auch die fetch_this methode muss den this-zeiger dann vom OperandStack oder so holen. Registriert man allerdings nur stdcall funktionen, dann fällt die fetch_this methode natürlich ganz weg.

Die execute methode kapselt im Prinzip nur den Methodenaufruf. Da gibt es allerdings zwei verschiedene Typen:
(a)Mit Rückgabetyp
(b)Ohne Rückgabetyp

Man kann die execute methode nicht so implementieren:
CPP:
R return_value = (*instance.*(method_pointer))(param1);


Denn wenn wir R=void haben, bekommen wir einen compiler-error.

Also muss man die methode für die Fälle a und b spezialisieren.
CPP:
// called if R != void
template<class RetType>
inline void execute(C* instance,P1& param1)
{
   stored_return_value.value = (*instance.*(method_pointer))(param1);
}
// called if R = void
template<>
inline void execute<void>(C* instance,P1& param1)
{
   (*instance.*(method_pointer))(param1);
}


Hier findet also der eigentliche aufruf statt. Die parameter instance und param1 eralten wir von der call-methode. Im Fall a speichern wir den Rückgabewert in einer Hilfsvariable, dessen Typ - keiner hätte es erwartet - wieder durch Templates definiert wurde.

CPP:
//==================================================
template<class R>
struct return_value
{
   R      value;
};
template<>
struct return_value<void>
{
};

Nichts besonderes. Im Falle R=void hat dieser Typ einfach keine Membervariable vom Typ R. Dadurch wird es uns möglich, den Rückgabewert in dem method_object objekt zu speichern.
edit: man kann den rückgabewert auch direkt auf den OperandStack pushen.

So, das reicht aber alles noch nicht für eine Registrierung von Methoden. Deswegen gibt es noch folgende Template-Klasse:
CPP:
template<class C>
class native_class
{
public:
   native_class(std::string class_name)
      : mName(class_name)
   {}

   template<class M>
   native_class<C>& method(std::string method_name,M ptr)
   {
      mMethodStorage.push_back(std::make_pair(method_name,new method_object<M>(ptr)));

      return *this;
   }

   void call_method(std::string name)
   {
      for(MethodStorage::iterator it=mMethodStorage.begin();it!=mMethodStorage.end();it++)
      {
         if(it->first.compare(name))
         {
            it->second->call();
            return;
         }
      }
   }

private:

   typedef std::list<std::pair<std::string,callable*> > MethodStorage;

   std::string         mName;
   MethodStorage      mMethodStorage;
};


Die klasse native_class soll eine c++-klasse mit ihren methoden kapseln. Der template-parameter C steht für die Klasse, die gekapselt werden soll.

mMethodStorage ist einfach eine std::list, die ein Werte paar der Form <std::string,callable*> speichert. D.h. wir haben in der Liste Tupel aus einem String und einem Pointer auf ein callable objekt (ja, std::map wäre vielleicht angebrachter).
Diese Liste soll also die Methoden der Klasse speichern.

Der Constructor ist nicht der rede wert.

Interessanter ist da die template-methode "method". Diese soll nämlich funktionspointer in der oben genannten liste speichern. Dies geschieht folgendermaßen:
Die Methode hat als template-parameter M den Typen des Methoden-Zeigers (rückgabetyp,klasse,parametertypen). Außerdem bekommt sie einen namen und den Methodenzeiger übergeben.

Die implementierung ist aber eigentlich nichts besonderes:
Es wird einfach ein listeneintrag erstellt mit dem mehtodennamen und dem methodenzeiger, welcher in einem method_object gekapselt wird.
Anschließend wird *this zurückgegeben. Wozu das gut ist wird man später noch sehen.

Die call methode ist auch interessant. Diese soll einen methodenaufruf einer registrierten methode verursachen:
Zuerst wird nach dem listeneintrag gesucht mit dem namen der im parameter übergeben wurde. Da der zweite eintrag im paar ein Zeiger auf ein callable objekt ist, kann man also "call" daran aufrufen, was wir dann auch tun.

Wie kann man das alles nun benutzen?

CPP:
struct test
{
   test(){}

   void foo(int i)
   {
      std::cout<<i<<std::endl;
   }

   int bar(float i)
   {
      std::cout<<i-3<<std::endl;

      return static_cast<int>(i-2.0f);
   }
};

int main()
{
   native_class<test> native_test("test");
   native_test
      .method("bar",&test::bar)
      .method("foo",&test::foo);

   native_test.call_method("bar");

   std::cin.get();

}


Das schöne an funktionstemplates ist, dass sie aus ihren parameter meist automatisch auf die template-parameter schließen kann, so dass man diese nicht explizit angeben muss.
native_class<C>::method ist so ein funktionstemplate. Der template parameter M wird automatisch aus dem übergebenen methodenzeiger abgeleitet.

Das Registrieren funktioniert nur für methoden mit bis zu n parametern. Allerdings kann n frei gewählt werden.

Aaah, meine Finger. Wer Fehler findet kriegt n Arschtritt Wink
Und wer es nicht versteht ist doof Razz

Ich habe das mal eben schnell runtergeschrieben, d.h. es könnte unverständlich sein, ich merke das ja nicht Wink .

Edit: Probleme die auftauchen werden:
Registrierung von Konstrutoren.
Tipp:
Erstellt einen templatisierten functor (den du auch für 0 bis n parameter spezialisierst) der eine instanz anlegt (eventuell mit placement new) und dabei einen Konstruktor aufruft.
_________________
Kochen ist ein NP-schweres Optimierungsproblem.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
Maxim
Senior JLI'ler



Anmeldedatum: 28.03.2004
Beiträge: 249

Medaillen: Keine

BeitragVerfasst am: 14.05.2006, 14:57    Titel: Antworten mit Zitat

Muss noch alles durchgehen. Danke.

Ich hab es auch selbst hinbekommen. Ich wusste doch dass es mit Assembler geht. Hab das hier gefunden:

CPP:
DWORD Call_stdcall( const void* args, size_t sz, DWORD func )
{
    DWORD rc;               // here's our return value...

    __asm
    {
        mov   ecx, sz       // get size of buffer
        mov   esi, args     // get buffer
        sub   esp, ecx      // allocate stack space
        mov   edi, esp      // start of destination stack frame
        shr   ecx, 2        // make it dwords
        rep   movsd         // copy it
        call  [func]        // call the function
        mov   rc,  eax      // save the return value
    }

    return ( rc );

}


von
http://www.drizzle.com/~scottb/publish/gpgems1_fubi.htm
Ich glaub den gleichen Artikel gibt es auch in Spieleprogrammierung Gems 1.

Auf jeden Fall funktioniert jetzt bei mir alles. Jahuu!!! Very Happy
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Kampfhund
Super JLI'ler


Alter: 42
Anmeldedatum: 20.07.2002
Beiträge: 408

Medaillen: Keine

BeitragVerfasst am: 14.05.2006, 18:46    Titel: Antworten mit Zitat

Maxim hat Folgendes geschrieben:

Ich glaub den gleichen Artikel gibt es auch in Spieleprogrammierung Gems 1.


Ja, steht drin. Allerdings ist die Luabind-Lösung viel besser:

CPP:
Function f;
f.name = "blub";
f.ptr = &blub;
f.parameters.push_back(PARAM_INTEGER);
f.parameters.push_back(...);
...


Sehr unsauber. Außerdem kann man nicht jeden beliebigen parameter übergeben.

Bei der Luabind-Lösung wird automatisch aus dem funktions-pointer die funktions-signatur gewonnen und daraus automatisch der code für das parameter-holen erstellt. Da gibt es kaum möglichkeiten Fehler zu machen. Außerdem kann man halt funktionen mit beliebigen parametertypen registrieren.
_________________
Kochen ist ein NP-schweres Optimierungsproblem.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Entwicklung Alle Zeiten sind GMT
Gehe zu Seite 1, 2  Weiter
Seite 1 von 2

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.


Powered by phpBB © 2001, 2005 phpBB Group
Deutsche Übersetzung von phpBB.de

Impressum