Hallo,
folgende Situation:
Ich habe einen kleinen Skript-Interpreter geschrieben (Visual C++ 2005),
der Scriptdateien abarbeiten kann. Der funktioniert auch schon prima.
Ich habe diesen Interpreter als Klasse implementiert und füttere diesen
mit Strings die dann abgearbeitet werden. Nun benötige ich auch die
Möglichkeit aus dem Script heraus eine "fremde" Funktion/Methode eines
Objekts aufzurufen. Beispiel:
(meine Skriptsprache)
1
ret=Call("funktionsname",p1,p2,p3);
Zunächst wird bevor der Interpreter gestartet wird eine seiner Methoden
aufgerufen:
Pseudocode:
und damit die Funktion "funktionsname" dem Interpreter bekannt gemacht.
Wenn der Interpreter also im Skript auf obige Skriptzeile trifft sucht
er in einer Tabelle nach "funktionsname". Wenn er einen Eintrag findet
soll die fremde Methode aufgerufen werden und die 3 Parameter angehängt
werden.
Nachdem die fremde Methode fertig ist, soll das Ergebnis an den
Interpreter zurückgeliefert werden, welcher das Ergebnis in "ret" für
die Weiterverarbeitung ablegt.
Soweit klar was ich vorhabe?
Wichtig ist mir dabei, dass die Interpreterklasse "eigenständig" bleibt
und z.B. nicht fremde Klassen darin inkludiert werden müssen. Genauso
andersrum soll die "fremde Klasse" bzw. Objekt nichts von dem
Interpreter wissen. (Hintergrund: Modularität und Wiederverwendbarkeit)
Eine übergeordnete Funktion inkludiert also beide Klassen (Interpreter
und die andere), registriert die Funktionen/Methoden beim Interpreter
und startet diesen anschließend.
Ich hoffe ihr könnt mir irgendwie helfen. Ein paar Tipps wären schonmal
ein guter Anfang :)
Vielen Dank!
PS: Mir ist klar, dass es sich hier um keine µC-Frage handelt sondern
VC++, obwohl der Skript-Interpreter letztlich doch über RS232 einen µC
ansteuert ... doch habe ich hier im Forum am ehesten die Hoffnung auf
kompetente Entwickler zu treffen ;-)
Hallo Alex,
Alex Wurst wrote:
> folgende Situation:> Ich habe einen kleinen Skript-Interpreter geschrieben (Visual C++ 2005),> der Scriptdateien abarbeiten kann.
Was für Script Dateien?
>Der funktioniert auch schon prima.> Ich habe diesen Interpreter als Klasse implementiert und füttere diesen> mit Strings die dann abgearbeitet werden.> und damit die Funktion "funktionsname" dem Interpreter bekannt gemacht.> Wenn der Interpreter also im Skript auf obige Skriptzeile trifft sucht> er in einer Tabelle nach "funktionsname". Wenn er einen Eintrag findet> soll die fremde Methode aufgerufen werden und die 3 Parameter angehängt> werden.
Was meinst du mit "fremde" Methode? Irgendwohin muss der Funktionszeiger
ja zeigen? Oder soll das so eine Art DLL Aufruf werden?
> Nachdem die fremde Methode fertig ist, soll das Ergebnis an den> Interpreter zurückgeliefert werden, welcher das Ergebnis in "ret" für> die Weiterverarbeitung ablegt.>> Soweit klar was ich vorhabe?
Naja, noch nicht ganz.
> Wichtig ist mir dabei, dass die Interpreterklasse "eigenständig" bleibt> und z.B. nicht fremde Klassen darin inkludiert werden müssen. Genauso> andersrum soll die "fremde Klasse" bzw. Objekt nichts von dem> Interpreter wissen. (Hintergrund: Modularität und Wiederverwendbarkeit)
Ok, das ist schon eher verständlich was du damit bezwecken willst.
> Eine übergeordnete Funktion inkludiert also beide Klassen (Interpreter> und die andere), registriert die Funktionen/Methoden beim Interpreter> und startet diesen anschließend.
Da fallen mir spontan 2 Entwurfsmuster aus der Softwareentwicklung ein:
- Visitor
- Observer
Google mal danach!
Was zum Verständnis ganz gut und wäre und wie man dir dann auch besser
helfen könnte wäre, wenn du ein kleines Klassen Diagramm hier
reinstellst.
Damit man weiß welche konkreten Klassen es gibt und wie diese
miteinander kommunizieren.
MFG
Das Visitor-Pattern ist, was du hier brauchst.
Einfach skizziert sieht es folgendermaßen aus:
Du definierst in einem speziellen Header ein abstrakte Klasse, die die
Schnittstelle für funktionsname definiert.
Der Interpreter liest diesen Header ein und ruft die Memberfunktion
funktionsname der abstrakten Klasse mit den entsprechenden Parametern
auf und behandelt das Ergebnis. (Über das Innenleben der Funktion muß er
nichts wissen.)
Eine konkrete Implementierung einer Funktion funktionsname geht
folgendermaßen:
Man baut eine Klasse, die von der abstrakten Klasse abgeleitet ist und
implementiert darin die Funktion funktionsname.
Alex Wurst wrote:
> Alles klar, ich werde nachher mal kurz Visio bemühen und ein kleines> Diagramm anfertigen. Melde mich dann nochmal.
Hey Alex,
ich kenne nicht alle deine Klassen, aber ich hab jetzt einfach mal "aus
dem Bauch heraus" ein Klassendiagramm mit Anlehnung an das Visitor
Pattern gezeichnet.
Zur Erklärung:
Object1 wäre z.B. eins deiner "fremden Objekte".
Mit acceptVisitor wird dein "fremdes Objekt" mit dem Visitor (also
Interpreter) verknüpft. Dieser kann nun über seine eigenen
Besuchsfunktionen auf deine fremden Objekte zugreifen. Da sie als
Pointer übergeben und gespeichert werden brauchst du keine Inkludierung
der Header-Datei. Da sie alle von einer primitiven Objektklasse
abgeleitet wurden kannst du auf Basisfunktionen dieses Objekts
zugreifen.
Nur mal aus Interesse:
Warum benötigst du diese Script Sprache bzw. was willst du damit genau
machen?
MFG
Also im Anhang mal ein Klassendiagramm. Kurze Erläuterung:
Ich habe eine Mfc-Applikation die sich um Datei öffnen/schließen usw.
kümmert. Nur MfcDlg inkludiert die beiden anderen Klassen (angedeutet
durch "uses"-Pfeile), die beiden Klassen "kennen" sich nicht. Durch den
Klick auf den Button im MfcDlg wird folgender Pseudocode ausgeführt:
1
CStringstrSatz;
2
GetDlgItemText(IDC_COMMAND,strSatz);//Scripttext aus Edit-Control holen
MyInterpreter.Run(&strSatz);//Script-Text an Interpreter übergeben
Der Interpreter verfügt über einen eigenen Stackspeicher, der
Rückgabewerte, Parameter, Zwischenergebnisse und Variablen verwaltet.
Eben so ähnlich wie C es auch macht, nur im kleinen Stil und nicht so
komplex. Wenn nun das Skript
1
ret=Call("funktionsname",a,b);
interpretiert wird, dann weiß der Interpreter anhand des Befehlsworts
"Call", dass an erster Position der Funktionsname (z.B. "Funktion1")
steht und er alle nachfolgenden Parameter bis zum ")" auf seinen Stack
schaufeln muß.
Er sucht dann in seiner Funktionstabelle, ob per AddFunction-Methode
eine fremde Funktion registriert wurde. Findet er einen Eintrag, ruft er
die Funktion1 auf und übergibt dieser einen Zeiger, der auf a im
Interpreter-Stack zeigt. Das heisst im Umkehrschluss die
"Funktions-Schnittstelle" von AndereKlasse muß genormt sein, damit der
C-Compiler nicht meckert und so aussehen: FunktionsName(Stack* Pointer).
Was AndereKlasse letztlich mit dem StackPointer macht, geht den
Interpreter nichts mehr an.
Die Funktion1 "weiß" z.B., dass sie 2 Parameter (p1,p2) benötigt, sie
nimmt also den Zeiger und holt sich von dieser Stackposition ausgehend
die Werte von a und b. Ermöglicht wird das über eine Datenstruktur die
beiden bekannt ist. Wenn Funktion1 seine Aufgabe erledigt hat, legt
diese ihren Returnwert an Zeiger auf a ab und wechselt wieder zurück zum
Interpreter, der den Returnwert von seinem Stack abruft und in "ret"
abspeichert. Danach - sofern vorhanden - werden die nachfolgenden
Skriptzeilen interpretiert.
Vielen Dank soweit mal für die Rückmeldungen, ich les mir das gleich mal
alles durch und befrage noch den Google dazu.
Zur Info wofür der ganze Zauber ist:
Ich brauche diesen Interpreter, der Skriptdateien (in einem eigenen
propritären Format) abarbeiten kann. Dabei beherrscht der Interpreter
die 3 Datentypen Integer, Char-Arrays und Strings. Er kann if...else und
while()-Konstrukte sowie Hilfsfunktionen a la sizeof() oder
AddressOf-Operator (&), Grundrechenarten usw. und ermöglicht dank
eigenem Stack eine beliebige Verschachtelungstiefe. Zusätzlich wird eben
noch oben genannte "Call"-Funktion benötigt um externe Aufrufe zu
realisieren. Das ganze brauche ich für eine Testsoftware welche
(automatisiert) Skripte abarbeitet und so z.B. einen Umwelttest eines
Embedded Geräts selbständig steuert und protokolliert. Da es sich aber
um völlig verschiedenartige Gerätetypen handelt (die unterschiedlicher
Tests bedürfen), aber alle ähnlich via RS232 angesprochen werden, habe
ich diese Skriptsprache entworfen. Das Bindeglied zwischen Interpreter
und RS232-Kommunikation wird dabei in "AndereKlasse" realisiert (z.B.
ein Protokoll), welches über Funktion1/2/3 seine Aufgaben erhält, damit
man später auch mal losgelöst in einem anderen Projekt den Interpreter
für seine Belange nutzen kann.
Es soll dazu dienen firmenweit eine "Standard-Testskriptsprache" zu
ermöglichen ohne jedes Mal erneut das Rad zu erfinden. Wichtig war dabei
eine sehr einfache und verständlichen Skriptsprache zu wählen, die auch
jemand benutzen kann, der eigentlich keine Ahnung von Programmierung
hat. Sprich, die gesamte Syntax sollte auf 1-2 DINA4 Seiten verständlich
gemacht werden. Aus genau diesem Grund habe ich bewusst keinen Windows
Scripting-Host oder Ahnliches in Betracht gezogen.
Ja von Lua hatte ich vorgestern das erste Mal gehört, also ein paar Tage
zu spät. Der Interpreter ist soweit fertig, es fehlt nur noch diese
call-Geschichte. Wobei da der Interpreter selbst auch schon fertig ist,
es geht nur noch um die Datenübergabe... Ich weiß nicht, wie ich meinem
Arbeitgeber vermitteln soll, plötzlich alles neu zu machen, obwohl es
schon fast fertig ist ;-)
Alex Wurst wrote:
> ...es fehlt nur noch diese> call-Geschichte. Wobei da der Interpreter selbst auch schon fertig ist,> es geht nur noch um die Datenübergabe... Ich weiß nicht, wie ich meinem> Arbeitgeber vermitteln soll, plötzlich alles neu zu machen, obwohl es> schon fast fertig ist ;-)
Hey Alex,
ich verstehe immer noch nicht ganz warum dieser Intpreter so
"kompliziert" sein muss. Klar, wenn du irgendwelche Variablen verwendest
müssen diese auch in deine Klasse gespiegelt werden. Warum aber ein
Stack? Ich sehe die Notwendigkeit noch nicht direkt.
Obwohl, wenn ich so darüber nachdenke.. Dein Skript kann bzw. soll ja
sehr universell sein, also musst du ja die Variablen und Zwischenwerte
irgendwie verwalten.
Was GENAU funktioniert denn bei dieser Call Geschichte nicht? Der Aufruf
der Funktion? Die Registrierung der Methoden? Die Übergabe der
Variablen?
Also, wenn ich jetzt mal deine Stack-Technik aufgreife musst du doch der
Funktion nur den Pointer auf den Stack geben und diese weiß ja dann wie
viel sie vom Stack runterholen muss und legt ihrerseits dann das
Ergebnis auf den Stack oben drauf ... ?!
So wie du es beschrieben hast, funktioniert doch alles? Oder waren das
nur deine theoretischen Überlegungen? ;-)
Wenn du es mit dem Visitor Pattern machst, brauchst du "Besucher
Funktionen" für ALLE möglichen Objekte die dein Skript unterstützt.
Kommt es dann zum Funktionsaufruf in deinem Skript wird eben
entsprechend die Besucher Funktion für das Objekt aufgerufen. In diesem
Fall würde ich aber keinen Stackpointer übergeben.. schließlich kennst
du ja die Schnittstelle, also weißt welche Parameter die Funktion
erwartet und holst die Werte schon vor dem Aufruf vom Stack und legst
dann auch selbst das Ergebnis auf den Stack... Das kommt natürlich ganz
darauf an wie flexibel deine Schnittstellen aussehen sollen...
Ah, ok langsam schimmert es mir was du vor hast... :-P
Deine primitive "Besucher Funktion" muss etwa so aussehen:
1
stack_p*functionCall(stack_p*p);
Hier kann quasi "alles rein" und "alles raus"... aber irgendwer, also
dein Interpreter muss ja trotzdem dann die Rückgabewerte entsprechend
casten...
Nur mal so aus Interesse wie hast du deinen Stack realisiert?
Alles 8 Bit? Also jeder Stackspeicherplatz ist ein Byte groß?
Oder ist jeder Speicherplatz 32 Bit breit und es wird immer nur so viel
genutzt wie gebraucht wird?
Schreib mal genau auf was deine Anforderungen an diese Schnittstelle
sind.
MFG
Eigentlich muss dein Interpreter in seiner "Liste" nur Pointer auf
"Fremd Objekte" verwalten. Stickwort Polymorphie.
D.h. in deiner Liste sind ALLE Objekte vom Typ object. Jedes dieser
Objekte ist in Wirklichkeit eine abgeleitete Variante von object aber
jedes unterstützt die (virtuelle) Funktion:
stack_p* functionCall (stack_p* p);
Dein Liste sähe also wie folgt aus
[FunctionId][Object Pointer]
über die FunctionId kommst du auf das Object.
[Script]
ret = Call(FunctionId, a, b);
[/Script]
Hier würde also im Hintergrund etwa sowas aufgerufen:
Damit hast du doch deine (universelle) Schnittstelle.
Und der Interpreter muss nix von den KONKRETEN Objekten wissen.
Dein Interpreter muss nur wissen, dass jedes Objekt diese functionCall
Schnittstelle unterstützt. Ansonsten muss der Interpreter noch wissen
wie er mit dem Rückgabe Pointer deiner Funktion umgehen musst.
MFG
Hallo Sebastian,
diesen Interpreter zu schreiben war für mich nicht weiter schwer und die
gröbste Arbeit in 2-3 Arbeitstagen erledigt. Ich hatte im Studium mal
einen Interpreter zur Lösung von Gauss-Algorithmen geschrieben, den
konnte ich ganz gut als Vorlage verwenden. Der Stack ist ziemlich
einfach realisiert, im Prinzip ein Array eines Structs. Dieser Struct
enthält den Datentyp. Anhand des Datentyps wird dann im selben Struct
der Wert (es gibt nur Integer) oder ein Pointer auf den Datentyp
abgelegt. Die Daten selbst (z.B. Char-Array oder String) werden auf dem
Heap abgelegt. Klingt alles vielleicht kompliziert, sind aber nur einige
Zeilen Code.
So wie ich es geschrieben habe, ist es wie ich es mir vorstelle und es
auch teilweise schon implementiert ist. Mein Kernproblem lag bisher
darin, dass z.B. die "AddFunction"-Methode einen Pointer auf ein Objekt
erwartet (muß ja irgendwo deklariert werden), aber es das Objekt ja gar
nicht kennt. Ein Teufelskreis irgendwie ;-)
So wie ich es jetzt verstanden habe, soll ich es so machen, dass
Interpreter einen abstrakten Objekttyp "object" kennt, also z.B.
1
intAddFunction(CStringFuncName,object*Target);
Die "AndereKlasse" ist dann von "object" abgeleitet und implementiert
eine Methode von "object". Der Interpreter ruft dann einfach die
virtuelle Methode von "object" auf, welche dann "weiß" was zu tun ist?
Klingt irgendwie logisch.
Hab ich das soweit richtig verstanden?
Es ist erstaunlich, dass ich dieses ganze Zeug mal studiert habe, aber
ich mache seit 3-4 Jahren eigentlich zu 95% nur noch "flaches" C auf
Embedded Systemen und bin irgendwie geschockt wie schnell das Wissen
einstaubt.... Ich denke ich werde mal wieder meine Aufschriebe von
damals rauskramen.
Vielen Dank soweit!
Hey Alex,
Alex Wurst wrote:
>...Der Stack ist ziemlich> einfach realisiert, im Prinzip ein Array eines Structs. Dieser Struct> enthält den Datentyp. Anhand des Datentyps wird dann im selben Struct> der Wert (es gibt nur Integer) oder ein Pointer auf den Datentyp> abgelegt. Die Daten selbst (z.B. Char-Array oder String) werden auf dem> Heap abgelegt. Klingt alles vielleicht kompliziert, sind aber nur einige> Zeilen Code.
Ok, ich kann's mir vorstellen :-) Ein Struct ist an dieser Stelle
natürlich "eleganter" als alles in 8 Bit aufzuteilen. Hab da zu sehr an
einen "hardware" Stack gedacht ;-)
> So wie ich es geschrieben habe, ist es wie ich es mir vorstelle und es> auch teilweise schon implementiert ist. Mein Kernproblem lag bisher> darin, dass z.B. die "AddFunction"-Methode einen Pointer auf ein Objekt> erwartet (muß ja irgendwo deklariert werden), aber es das Objekt ja gar> nicht kennt. Ein Teufelskreis irgendwie ;-)
Was du aber umgehen kannst, wenn du mit einem abstrakten Basistyp wie
"object" arbeitest.
> So wie ich es jetzt verstanden habe, soll ich es so machen, dass> Interpreter einen abstrakten Objekttyp "object" kennt, also z.B.>>
1
>intAddFunction(CStringFuncName,object*Target);
2
>
Genau.
> Die "AndereKlasse" ist dann von "object" abgeleitet und implementiert> eine Methode von "object". Der Interpreter ruft dann einfach die> virtuelle Methode von "object" auf, welche dann "weiß" was zu tun ist?
Richtig, durch die standardisierte Schnittstelle kannst du au jedes
abgeleitete Objekt zugreifen, welches vom Basistyp abgeleitet wurde.
> Klingt irgendwie logisch.
Klingt logisch, is aber so. :-)
> Hab ich das soweit richtig verstanden?
Du hast es verstanden. ;-)
> Es ist erstaunlich, dass ich dieses ganze Zeug mal studiert habe, aber> ich mache seit 3-4 Jahren eigentlich zu 95% nur noch "flaches" C auf> Embedded Systemen und bin irgendwie geschockt wie schnell das Wissen> einstaubt.... Ich denke ich werde mal wieder meine Aufschriebe von> damals rauskramen.
Das kenn ich, ich entwickle zur Zeit auch nur "flaches" C für
Mikrocontroller. Bei mir wird das C++ Wissen auch (leider) immer
weniger..
> Vielen Dank soweit!
Kein Problem.
Ciao
Alex Wurst wrote:
> Die "AndereKlasse" ist dann von "object" abgeleitet und implementiert> eine Methode von "object". Der Interpreter ruft dann einfach die> virtuelle Methode von "object" auf, welche dann "weiß" was zu tun ist?>> Klingt irgendwie logisch.>> Hab ich das soweit richtig verstanden?
Jep.
Genau das ist dann die Idee eines 'Interface'.
Ein Interface ist einfach nur eine abstrakte Klasse mit lauter
virtuellen Funktionen.
Deine reale Klasse leitet von diesem Interface ab und implementiert
damit das Interface. Es könnte auch noch andere Interfaces
implementieren - das ist für mich so ziemlich der einzige Fall in dem
ich Mehrfachvererbung einsetze.
Der springende Punkt: Für deinen Interpreter ist der tatsächlich
Klassentyp des angehängten Objektes völlig uninteressant, solange es nur
dieses Interface implementiert.
Hallo,
ich hab es nun exakt so realisiert wie im letzten Post formuliert
(Stichwort abstrakte "object"-Klasse) und es funktioniert einwandfrei.
Hatte noch einen kleine "Denkste!" bei der Methodendefinition drin, aber
ging dann doch. Es funktioniert: Ich kann per
1
res=call("funcname",1,2,3,4,5,6);
übergeben und die Methode des Objekts klamüsert dann den Stack
auseinander. Nach dem Aufruf räumt der Interpreter erstmal den Stack auf
und speichert den Rückgabewert der aufgerufenen Methode drauf. Somit
kann ich jetzt auch so Konstrukte wie: