Ich habe ein AVR mit der seriellen Schnittstelle an einem PC hängen. Vom
PC kommen unterschiedliche Befehle mit Parametern. Die Liste möglicher
Befehle (bis jetzt insgesamt 67) unterbringe ich in einem Array aus
Strings. Ferner erkenne ich die Befehle in einer Schleife mit strcmp.
Der Schleifenzähler ist dann der Index des Befehls. Bis hierhin ist
alles elegant und funktionsfähig. Jetz muss ich die Befehle ausführen.
Gibt es in C (WinAVR-gcc) eine Möglichkeit ohne switch und 67 x case
auszukommen?
Also vielleicht 67 Funktionen mit gleichen Namen (z.B:
befehlsausführung(1), befehlsausführung(2) ), die nur an den Parametern
unterschieden werden. Dann kann ich im Hauptprogramm schreiben
befehlsausführung(Index_des_Befehls); und schon wird die richtige
Funktion angesprungen. Ich glaube, das heisst "Überladen"?
Oder ein Array aus Funktionen:
int befehlsausführung() [];
befehlsausführung()[1] { Code; };
Und Aufruf:
befehlsausführung()[Index_des_Befehls];
So eine ähnliche Funktionalität kannst du in der Tat
aufbauen.
Voraussetzung: Alle Funktionen haben die gleiche Signatur.
Du definierst dir einen Funktionspointer Datentyp.
(Ist nicht zwingend notwendig, vereinfacht die Sache aber).
Sagen wir mal deine Funktionen sind alle void - void
typedef void (*FunctPtr)( void );
(Wenn deine Funktionen einen int nehmen würden und einen
unsigned char liefern, würde das dann heissen:
typedef unsigned char (*FuncPtr)( int );
)
FuncPtr ist der Name das Datentyps.
Weiters hast du noch du Funktionen:
void Funktion1()
{
...
}
void Funktion2()
{
...
}
Jetzt definierst du dir noch ein Array mit
Funktionspointern und initialisierst es
mit - Pointern auf die Funktionen.
FuncPtr Funktionen[] = { Funktion1, Funktion2 };
Um eine Funktion dann indirekt über einen
Funktionspointer aufzurufen, benutzt du
ganz normale Funktionssyntax.
Funktionen[i]();
Du kannst das ganze aber noch eleganter machen.
Im Moment hast du ein Array aus Strings.
Mach da ein Array aus Strukturen draus:
typedef void (*FuncPtr)(void);
struct Befehl {
char Text[20];
FuncPtr Funktion;
};
void FuncCopy()
{
...
}
void FuncPaste()
{
...
}
struct Befehl[] =
{
{ "Copy", FuncCopy },
{ "Paste", FuncPaste }
};
Auf die Art bleibt zusammen, was zusammen gehört.
Der Text, wie der Befehl heist und der Funktionspointer,
der für die Umsetzung des Befehls sorgt.
Deine Suchschleife lautet dann:
FuncPtr Find( const char* String )
{
for( int i = 0; i < Anzahl_Befehle; ++i ) {
if( strcmp( String, Befehl[i].Text ) == 0 )
return Befehl[i].Funktion;
}
return NULL;
}
Und verwendet wird es dann so:
FuncPtr Func;
char UARTBefehl[20];
...
Func = Find( UARTBefehl );
if( Func != NULL )
Func();
Vielen Dank, Karl heinz! Ich versuche jetzt mal die zweite Variante, da
ich sowieso schon ein struct habe (ich habe dort u.a. den
Parameter-Datentyp, min und max zu den Parametern usw.).
Und wie sieht der typdef aus wenn der FuncPtr nicht auf eine void
funktion zeigt sondern selbst vom Typ FunkPtr ist?
siehe Beispiel in der Anlage
Danke schonmal
Da hast du mich an meine Grenzen gebracht.
Das krieg ich auch nicht hin. Wenn ich mich
richtig erinnere gibt es aber dafür eine Lösung.
Hab ich mal vor Jahren in comp.lang.c gesehen.
Leider nein.
Alle benutzten Compiler melden zwar OK (0 Error, 0 Warning).
Das erzeugte Programm ist aber nicht lauffähig, weil es sich immer
wieder selbst aufruft bis zum PC: "Stack Overflow" bzw. AVR: "Watchdog
Reset".
Getestet ohne die for(;;) Schleife.
Hat noch jemand andere Ideen?
Cfrager wrote:
> Das erzeugte Programm ist aber nicht lauffähig, weil es sich immer> wieder selbst aufruft bis zum PC: "Stack Overflow" bzw. AVR: "Watchdog> Reset".> Getestet ohne die for(;;) Schleife.
Die for-Schleife muß natürlich sein, Du willst doch die Funktion des
Rückgabewertes aufrufen, erst nachdem die vorherige beeendet wurde.
Anders gehts nicht.
Die Funktionen dürfen sich nicht selber aufrufen, das wäre ja eine
Rekursion ohne Abbruchbedingung. Da muß unbedingt der Stack überlaufen.
Und dann wäre ja ein Funktionspointer als Returnwert völlig überflüssig
(never reached).
Peter
Ich weiß auch nicht was der Compiler sich da zusammenreimt.
Alleine die Zeile "fp = test1();" reicht für den ungewollten Endlos
Betrieb aus, obwohl das doch nur die Initialisiereung seien sollte.
Erst der folgende Aufruf "fp = (ffunc)fp();" sollte dann diese Funktion
genau einmal ausführen und als Ergebnis die nächste auszuführende
Funktion liefern
- ggf. auch sichselbst.
Joe
Cfrager wrote:
> Alleine die Zeile "fp = test1();" reicht für den ungewollten Endlos> Betrieb aus, obwohl das doch nur die Initialisiereung seien sollte.
Die Zuweisung 0x1234 ist natürlich nur ein Platzhalter.
Die mußt Du also durch die Adresse einer existierenden Funktion
ersetzen.
Ich habs nur deshalb gemacht, um den erzeugten Assembler anzusehen und
der sieht gut aus.
Peter
So wies aussieht, gehts ohne cast überhaupt nicht.
Mir fällt aich nichts besseres ein.
Das einzige as ich anders machen würde:
Anstatt der 2 typedefs würde ich die Funktion
einen void Pointer zurückliefern lassen:
1
typedefvoid*(*ffunc)(void);
2
3
void*test1(void)
4
{
5
returnrun;
6
}
7
8
void*run(void)
9
{
10
returnrun;
11
}
12
13
voidtest(void)
14
{
15
void*fp;
16
fp=test1();
17
18
for(;;)
19
fp=(ffunc)fp();
20
}
Der muss zwar auch gecastet werden, aber anders als mit
dem ursprünglichen Funktionspointer Rückgabewerte muss
er gecastet werden. Mit dem ursprünglichen func-Pointer
könne man theoretisch Schindluder treiben, wenn man ihn nicht
entsprechend zurechtcastet.
Sorry für die verzögerte Reaktion, aber...
das ist nicht was ich meinte/wollte.
Bis ca. 2002 - "alte Compiler" PC: Borland C 80x1: Keil-C
hat folgende Variante erfolgreich funktioniert:
void* start(void); // Prototyp
void* run(void); // Prototyp
void* ende(void; // Prototyp
void* (*fp)(void); // globaler Zeiger auf Funktionen
void* start(void){
return run; // damit gehts weiter 1)
}
void* run(void){
return ende; // damit gehts weiter 1)
}
void* ende(void){
return ende; // fertig, hier bleiben 1)
}
void test(void){
fp=start; // initialisieren, noch nicht ausführen
for(;;)
fp=(*fp)(); // ausführen start, run, ende, ende,.... 2)
}
Mit "neueren Compilern" PC: MS Visual AVR: IAR-C (strengere Typprüfung)
kamen dann die "Warnings" und "Errors" etwa so:
1) return value type does not match the function type
2) a value of type "void *" cannot be assigned to an entity of type
"void *(*)()"
Mangels besserer C-Kenntnisse bin ich dann auf 'emun', 'switch' und
Tabellen -wie im oberen Lösungsvorschlag- ausgewichen.
Ich hatte gehofft mir kann hier jemand mit einer "sauberen C
Formulierung"
helfen.
Joe
Es gibt keine 'saubere C Formulierung'. Mindestens ein
cast ist immer nötig. Und genau das sagt dir der Compiler
mit den Fehlermeldungen.
Im Grunde drehen sich beide Fehlermeldungen um die selbe
Sache: Die Datentypen sind nicht kompatibel. Zumindest nicht
auf dem Papier für den Compiler. Da du aber weist, dass alles
soweit richtig ist und stimmt, musst du den Compiler mit
einem cast überstimmen.
@Jörg
Jörg, du kennst den C Standard besser als ich. Früher malö
war es legal einen beliebigen Pointertyp auf einen void*
zuzuweisen. Ist das mit C99 gefallen und an C++ angeglichen worden?
Die Methode mit den void-Zeigern würde ich vermeiden, da nicht
garantiert ist das Zeiger auf Objekte und Zeiger auf Funktionen die
gleiche Größe haben.
In C++ würde man einfach eine Proxy-Klasse definieren
Ich hab sowas mal gemacht, siehe Anhang. Braucht so gut wie kein RAM.
Einfach erweiterbar. Parameter an die Unterfunktionen werden als
argc/argv übergeben.
Also, das Programm von Marko B. ist genau das, was ich brauche. Ich habe
es auch ohne Veränderung zum laufen gebracht, allerdings nur auf dem PC
(gcc 3.4.4 unter Cygwin). Die Implementierung für den AVR ist etwas
schwieriger, weil
1. das Array in den Programmspeicher muss: const command_t PROGMEM
mycmds[] = { ... };,
2. deshalb die strcmp_P anstelle der strcmp verwendet werden muss,
3. die Daten über Funktionen, wie pgm_read_byte usw. gelesen werden
müssen und
4. die entsprechende Funktion irgendwie anders aufgerufen werden muss.
Aber wie?
Ich habe es gelöst. Eine Möglichkeit ist es, die Adresse der Funktion
mittels pgm_read_word rauszulesen und an einen Zwischenpointer zu
übergeben, der dann aufgerufen wird:
fptr temppointer;
...
temppointer = (fptr)pgm_read_word(&mycmds[i].function);
temppointer(sargc, sargv);
...