|
JLI Spieleprogrammierung
|
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen |
Autor |
Nachricht |
Maxim Senior JLI'ler
Anmeldedatum: 28.03.2004 Beiträge: 249
Medaillen: Keine
|
Verfasst am: 13.05.2006, 11:15 Titel: Funktion ruft beliebige Funktion auf |
|
|
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 |
|
|
Dragon Super JLI'ler
Alter: 38 Anmeldedatum: 24.05.2004 Beiträge: 340 Wohnort: Sachsen Medaillen: Keine
|
Verfasst am: 13.05.2006, 12:07 Titel: Re: Funktion ruft beliebige Funktion auf |
|
|
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 |
|
|
Kampfhund Super JLI'ler
Alter: 42 Anmeldedatum: 20.07.2002 Beiträge: 408
Medaillen: Keine
|
Verfasst am: 13.05.2006, 12:11 Titel: |
|
|
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 |
|
|
GreveN JLI Master
Alter: 38 Anmeldedatum: 08.01.2004 Beiträge: 901 Wohnort: Sachsen - Dresden Medaillen: Keine
|
Verfasst am: 13.05.2006, 12:17 Titel: |
|
|
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 |
|
|
Maxim Senior JLI'ler
Anmeldedatum: 28.03.2004 Beiträge: 249
Medaillen: Keine
|
Verfasst am: 13.05.2006, 12:29 Titel: |
|
|
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 |
|
|
Kampfhund Super JLI'ler
Alter: 42 Anmeldedatum: 20.07.2002 Beiträge: 408
Medaillen: Keine
|
Verfasst am: 13.05.2006, 12:40 Titel: |
|
|
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 |
|
|
Maxim Senior JLI'ler
Anmeldedatum: 28.03.2004 Beiträge: 249
Medaillen: Keine
|
Verfasst am: 13.05.2006, 13:02 Titel: |
|
|
Im groben....schon |
|
Nach oben |
|
|
GreveN JLI Master
Alter: 38 Anmeldedatum: 08.01.2004 Beiträge: 901 Wohnort: Sachsen - Dresden Medaillen: Keine
|
Verfasst am: 13.05.2006, 13:05 Titel: |
|
|
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 |
|
|
Maxim Senior JLI'ler
Anmeldedatum: 28.03.2004 Beiträge: 249
Medaillen: Keine
|
Verfasst am: 13.05.2006, 13:13 Titel: |
|
|
verstehe
Die Anzahl der Parameter muss aber variabel sein. |
|
Nach oben |
|
|
GreveN JLI Master
Alter: 38 Anmeldedatum: 08.01.2004 Beiträge: 901 Wohnort: Sachsen - Dresden Medaillen: Keine
|
Verfasst am: 13.05.2006, 13:20 Titel: |
|
|
Verdammt, hab' ich nicht mitgekriegt.
Wenn das mit sprachinternen Mitteln geht, würde mich auch mal interessieren wie.
Vielleicht irgendeinen Container übergeben, der die eigentlich Funktionsargumente irgendwie beinhaltet?
Achja, was willst du eigentlich machen, vielleicht gibts eine bessere Lösung? |
|
Nach oben |
|
|
Maxim Senior JLI'ler
Anmeldedatum: 28.03.2004 Beiträge: 249
Medaillen: Keine
|
Verfasst am: 13.05.2006, 13:31 Titel: |
|
|
eine kleine script-sprache(funktionen aufrufen und mit variablen rumspielen)
funktioniert schon wunderbar, aber nur für vorgegebene funktion |
|
Nach oben |
|
|
Kampfhund Super JLI'ler
Alter: 42 Anmeldedatum: 20.07.2002 Beiträge: 408
Medaillen: Keine
|
Verfasst am: 13.05.2006, 13:48 Titel: |
|
|
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
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 |
|
|
Kampfhund Super JLI'ler
Alter: 42 Anmeldedatum: 20.07.2002 Beiträge: 408
Medaillen: Keine
|
Verfasst am: 13.05.2006, 15:10 Titel: |
|
|
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:
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
Und wer es nicht versteht ist doof
Ich habe das mal eben schnell runtergeschrieben, d.h. es könnte unverständlich sein, ich merke das ja nicht .
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 |
|
|
Maxim Senior JLI'ler
Anmeldedatum: 28.03.2004 Beiträge: 249
Medaillen: Keine
|
Verfasst am: 14.05.2006, 14:57 Titel: |
|
|
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!!! |
|
Nach oben |
|
|
Kampfhund Super JLI'ler
Alter: 42 Anmeldedatum: 20.07.2002 Beiträge: 408
Medaillen: Keine
|
Verfasst am: 14.05.2006, 18:46 Titel: |
|
|
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 |
|
|
|
|
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
|