Forum: Mikrocontroller und Digitale Elektronik C++: Funktionspointer auf "fremde" Methode einer Klasse


von Alexander I. (daedalus)


Lesenswert?

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:
1
Interpreter.AddFunction("funktionsname", *fremdesobjekt.methode(int,int,int))

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 ;-)

von Sebastian B. (mircobolle)


Lesenswert?

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

von Alexander I. (daedalus)


Lesenswert?

Alles klar, ich werde nachher mal kurz Visio bemühen und ein kleines 
Diagramm anfertigen. Melde mich dann nochmal.

von Uhu U. (uhu)


Lesenswert?

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.

von Sebastian B. (mircobolle)


Angehängte Dateien:

Lesenswert?

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

von Alexander I. (daedalus)


Angehängte Dateien:

Lesenswert?

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
CString strSatz;
2
GetDlgItemText(IDC_COMMAND, strSatz); //Scripttext aus Edit-Control holen
3
Interpreter MyInterpreter; //Interpreterklasse instanzieren
4
MyInterpreter.Initialize(); //Stack löschen, Parameterlisten löschen
5
AndereKlasse MyAndereKlasse(); //AndereKlasse instanzieren
6
7
MyInterpreter.AddFunction("Funktion1", &MyAndereKlasse.Funktion1());
8
MyInterpreter.AddFunction("Funktion2", &MyAndereKlasse.Funktion2());
9
MyInterpreter.AddFunction("Funktion3", &MyAndereKlasse.Funktion3());
10
11
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.

von ... (Gast)


Lesenswert?

Nimm das hier und lass die Eigenentwicklung:

http://www.lua.org

http://de.wikipedia.org/wiki/Lua

von Alexander I. (daedalus)


Lesenswert?

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 ;-)

von Sebastian B. (mircobolle)


Lesenswert?

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

von Sebastian B. (mircobolle)


Lesenswert?

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:
1
ret_stack_pointer = ObjectArray[FunctionId]->functionCall (stack_pointer);

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

von Alexander I. (daedalus)


Lesenswert?

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
int AddFunction(CString FuncName, 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!

von Sebastian B. (mircobolle)


Lesenswert?

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
> int AddFunction(CString FuncName, 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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Alexander I. (daedalus)


Lesenswert?

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:
1
res = call("funcname",1,2,3,4,5,call("funcname",1,2));

abbilden. Entspricht genau dem was ich erreichen wollte.

Vielen Dank für die kompetente Hilfe, obwohl das hier kein OOP-lastiges 
Forum ist!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.