Forum: Compiler & IDEs Übergabe Funktionspointer und Struct


von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Hallo,

ich möchte eine Bibliothek möglichst variabel gestalten und komme dabei 
an meine Grenzen in der Programmierung.

Speziell möchte ich verschiedene Displaytreiber an eine übergeordnete 
Grafik-Bibliothek übergeben, da ich 3 Grafik-Displays (2 verschiedene 
Typen) an einen Controller anschließen muss.

Der Treiber für die Displays ist so Aufgebaut, dass immer ein Struct mit 
allen Informationen übergeben wird.
1
typedef struct xxx_data_t  {
2
  uint8_t      pin_cs;
3
  uint8_t      pin_rs;
4
  uint8_t      pin_dc;
5
  PORT_t      *port;
6
  SPI_Master_t  *spi;
7
}xxx_data_t;
8
9
// BSP
10
ili9341_setPixel(&ili9341_1, 123, 240); // zeichne Pixel

Jetzt möchte ich zum Beispiel mit meiner Grafikbibliothek etwas 
zeichnen, Dazu muss ich den Funktionspointer und das Struct mit den 
Schnittstellen Infos übergeben.
1
void grafiklib(fctptr, struct)
2
{
3
    // mach etwas
4
    fctptr(struct, 123, 240);
5
}

Mein Problem ist, dass ich verschiedene Displaytreiber mit der Grafiklib 
verwenden möchte (ili9341, S65, Nokia, ...). Die Grafikfunktionen der 
Treiber sind immer gleich. Wie kann ich das in C realisieren, denn ich 
kann ja der Funktion "void grafiklib(fctptr, struct)" nicht verschiedene 
typen (structs) übergeben.
... oder wie kann ich das anders aufbauen.


Ich hoffe mein Problem ist so einigermaßen klar geworden.

von Klaus W. (mfgkw)


Lesenswert?

Martin J. schrieb:
> Ich hoffe mein Problem ist so einigermaßen klar geworden.

Sorry, mir leider nicht.

Das fctptr im obigen Beispiel ist was? Eine Funktion von einem fremden 
Treiber, den du nur übernimmst?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1) Origanisiere die Daten für die verschiedenen Treiber so, dass sie 
alle das gleiche Layout haben.  Evtl. ist dann Info in den Daten die ein 
Treiber nicht braucht.

oder

2) Lege die Strukturen in eine Union.  Jede Struktur und die Union haben 
als erstes Element ein Datum, dass die Daten identifiziert:
 
1
typedef union
2
{
3
    int id;
4
5
    struct
6
    {
7
        int id;
8
        ...
9
    } foo;
10
    struct
11
    {
12
        int id;
13
        ...
14
    } bar;
15
    ...
16
} data_t;
17
18
void f (data_t *data)
19
{
20
    if (data->id == DATA_FOO)
21
    {
22
        // use data->foo
23
    }
24
}

oder

3) Nimm einen void* und caste zu dem Datenzeiger der gebraucht wird 
(nicht so toll).

von Klaus W. (mfgkw)


Lesenswert?

Ich glaube, mir dämmert dein Problem.

Die struct soll sich von Treiber zu Treiber irgendwie unterscheiden, 
trotzdem willst du sie über dieselbe Parameterliste bringen, und 
letztlich landet sie pber den Funktionspointer als Parameter bei der 
richtigen Funktion?

Dann wäre evtl. eine union der richtige Weg:
Du schreibst für jeden Treiber die struct, machst von allen eine union 
und übergibst jeweils den Funktionszeiger und einen Zeiger auf die 
union.
Die Funktion nimmt sich dann aus der union das, was sie braucht.

von Programmierer (Gast)


Lesenswert?

Das ist der Anwendungsfall für C++ und virtuelle Funktionen. 
Einfacher, kürzer, fehlersicherer, und effizienter als die C-Wurstelei 
mit Funktionspointern.
1
class DisplayDriver {
2
  public:
3
    virtual void setPixel (int x, int y, int color) = 0;
4
    /* hier weitere Funktionen die jedes Display unterstützen muss, zB Bereiche ausmalen, ein/aus schalten etc. */
5
};
6
7
class Driverili9341 : public DisplayDriver {
8
  public:
9
    virtual void setPixel (int x, int y, int color) {
10
      /* .. display-spezifisches Pixelsetzen */
11
    }
12
    /* Hier o.g. weitere Funktionen unterbringen */
13
    
14
    /* Hier displayspezifische Variablen unterbringen, die sonst in das struct kommen würden */
15
    uint8_t      pin_cs;
16
    uint8_t      pin_rs;
17
    uint8_t      pin_dc;
18
    PORT_t      *port;
19
    SPI_Master_t  *spi;
20
};
21
22
// Funktion der GrafikLib die mit beliebigen Display-Typen funktioniert.
23
void grafikLib_drawText (DisplayDriver& display, int x, int y, const char* text) {
24
  // Render-Algorithmus ...
25
  
26
  // Ein gerendertes Pixel setzen. Hier wird automatisch die richtige Funktion der abgeleiteten Klasse, im Beispiel Driverili9341, aufgerufen.
27
  display.setPixel (x, y, 42);
28
}
29
30
int main () {
31
  // Einen bestimmten Display-Treiber anlegen
32
  Driverili9341 display;
33
  // Die allgemeine Text-Render-Funktion auf dieses spezielle Display anwenden
34
  grafikLib_drawText (&display);
35
}

von Klaus W. (mfgkw)


Lesenswert?

Programmierer schrieb:
> Das ist der Anwendungsfall für C++

ja, aber Masochismus ist verbreitet, das muß man akzeoptieren :-)

von Programmierer (Gast)


Lesenswert?

Klaus Wachtler schrieb:
> ja, aber Masochismus ist verbreitet, das muß man akzeoptieren :-)
Ich lasse mir zwar auch gerne den Hintern versohlen, aber so schlimm 
dass ich so etwas in C machen würde ist es noch nicht!

von Markus F. (mfro)


Lesenswert?

Nehmen wir an, Du hast folgende "Treiberfunktionen":
1
void init_driver1(void);
2
void init_driver2(void);
3
void exit_driver1(void);
4
void exit_driver2(void);
5
void draw_line_driver1(int x, int y, int x2, int y2);
6
void draw_line_driver2(int x, int y, int x2, int y2);

Dann bastelst Du dir ein struct, daß die Funktionszeiger aufnehmen kann:
1
struct driver_entry
2
{
3
    void (*init)();
4
    void (*exit)();
5
    void (*draw_line)(int x, int y, int x1, int x2);
6
    /* ... */
7
};

...und initialisierst ein Array aus solchen structs, über das Du 
zugreifen kannst:
1
static struct driver_entry drivers[] =
2
{
3
    { 
4
        init_driver1,
5
        exit_driver1,
6
        draw_line_driver1,
7
    },
8
    {
9
        init_driver2,
10
        exit_driver2,
11
        draw_line_driver2,
12
    }
13
};
14
static int num_drivers = sizeof(drivers) / sizeof(driver_entry);

Jetzt kannst Du nach Belieben damit rumhantieren:
1
int main(void)
2
{
3
    struct driver_entry *my_driver = &drivers[1]; /* nehmen wir mal den */
4
5
    my_driver->init();
6
    my_driver->draw_line(0, 0, 10, 10);
7
    my_driver->exit();
8
}

Sooo viel schöner ist das in C++ auch nicht. Und wenn die 
unterschiedlichen "Treiber" unterschiedliche Funktionen brauchen, muß 
man halt noch eine union dazwischen hängen.

von Programmierer (Gast)


Lesenswert?

Markus F. schrieb:
> Und wenn die
> unterschiedlichen "Treiber" unterschiedliche Funktionen brauchen, muß
> man halt noch eine union dazwischen hängen.
Und wenn ein Treiber viel mehr Speicher braucht als die anderen, muss 
man für jeden anderen genauso viel Speicher verschwenden, da ein union 
immer mindestens so groß ist wie das größte Element...

Markus F. schrieb:
> Sooo viel schöner ist das in C++ auch nicht.
Immerhin muss man die vtable (dein drivers array) nicht von Hand 
anlegen. Wenn man mehrere ähnliche Treiber hat, kann man eine abstrakte 
Zwischen-Klasse anlegen, und dann spart man erst Recht Tipparbeit, und 
geht einer größeren Fehlerquelle aus dem Weg... Warum auf den Komfort 
verzichten? Warum wollen immer alle um jeden Preis C++ vermeiden, auch 
wenn es perfekt geeignet ist?

von Markus F. (mfro)


Lesenswert?

Programmierer schrieb:
> Warum wollen immer alle um jeden Preis C++ vermeiden, auch
> wenn es perfekt geeignet ist?

Ich hätte das (möglicherweise) tatsächlich mit C++ gemacht. 
Möglicherweise sogar obwohl Libraries mit C++ manchmal überhaupt 
keinen Spaß machen...

Der Fragesteller hat aber offensichtlich mit C schon seine Probleme, 
warum ihm also 'nen Daimler aufschwatzen, wenn er eigentlich ein Fahrrad 
wollte? ;)

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Markus F. schrieb:
> Ich hätte das (möglicherweise) tatsächlich mit C++ gemacht.
> Möglicherweise sogar obwohl Libraries mit C++ manchmal überhaupt
> keinen Spaß machen...
Man kann in jeder Sprache Murks produzieren...
> Der Fragesteller hat aber offensichtlich mit C schon seine Probleme,
> warum ihm also 'nen Daimler aufschwatzen, wenn er eigentlich ein Fahrrad
> wollte? ;)
Vielleicht weil er damit schneller und bequemer über die Alpen kommt...

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Klaus Wachtler schrieb:
> Ich glaube, mir dämmert dein Problem.

Genau :-)


Allen schon mal vielen Dank für die Ideen. Ja man könnte das auch schön
elegant in C++ realisieren, doch dann müsste ich meine ganzen anderen
Bibliotheken ändern und darauf habe ich grad keine Lust. ;-)
Am Ende ist die Verwendung der Structs für die Variable
SPI-Schnittstelle der Treiber ja auch nur ein Pseudo C++, so dass man
weiter C verwenden kann.

Ich hatte nur gedacht, dass es für solche Fälle noch andere
Möglichkeiten in C gibt. Immerhin wurden auch sehr Komplexe Programe in
C geschrieben, die eine ähnliche Flexibilität erfordern.

Danke.

von Markus F. (mfro)


Lesenswert?

... dann machen wir die vtable eben vollends fertig:
1
extern void text_init(void);
2
extern void text_exit(void);
3
extern void text_print(int x, int y, char *txt);
4
extern void graf_init(void);
5
extern void graf_exit(void);
6
extern void draw_line(int x, int y, int x1, int y1);
7
8
enum driver_type
9
{
10
    GRAPHICS_DRIVER,
11
    TEXT_DRIVER
12
};
13
14
struct graphics_driver
15
{
16
    void (*init)();
17
    void (*exit)();
18
    void (*draw_line)(int x, int y, int x1, int x2);
19
    /* ... */
20
};
21
22
struct char_driver
23
{
24
    void (*init)();
25
    void (*exit)();
26
    void (*print)(int x, int y, char *txt);
27
};
28
29
static struct graphics_driver driver1 =
30
{
31
    .init = graf_init,
32
    .exit = graf_exit,
33
    .draw_line = draw_line,
34
};
35
36
static struct char_driver driver2 =
37
{
38
    .init = text_init,
39
    .exit = text_exit,
40
    .print = text_print,
41
};
42
43
struct driver
44
{
45
    enum driver_type type;
46
    union
47
    {
48
        struct graphics_driver *gd;
49
        struct char_driver *cd;
50
    };
51
};
52
53
struct driver drivers[] =
54
{
55
    { .type = GRAPHICS_DRIVER, { .gd = &driver1 }},
56
    { .type = TEXT_DRIVER, { .cd = &driver2 }},
57
};
Nicht unbedingt schön, aber tun tut's ;)

von Programmierer (Gast)


Lesenswert?

Martin J. schrieb:
> doch dann müsste ich meine ganzen anderen
> Bibliotheken ändern und darauf habe ich grad keine Lust. ;-)
Was musst du denn da alles ändern? Hast du 1000x die Bezeichner "this" 
und "class" verwendet? Die paar fehlenden void* -> T* casts findet der 
Compiler und sie sind schnell behoben. Außerdem spricht auch nichts 
dagegen, den Rest als C zu kompilieren...

von Amateur (Gast)


Lesenswert?

Funktionszeiger gibt es seit Anno Tobak.

Wenn Du sowieso eine Struktur initialisierst, spendiere ihr doch dabei 
gleich die passende Funktion.

Leider ist das "Steinzeit-C".


Virtuelle Funktionen gibt es zwar nicht ganz so lange (C++), aber 
manchmal hilft es eine Pupille ins nächst verfügbare C-Buch zu 
schmeißen.
Ist ganz einfach; beiß nicht.

Es gibt wohl kaum Lehrbücher, in denen gerade am Beispiel von grafischen 
Ausgaben, diese Mechanismen nicht erläutert werden. Da wird zwar oft 
eine virtuelle Funktion mit Namen print verwendet, mit der dann ein 
Etwas (Punkt, Kreis, Linie usw.) ausgegeben wird, das Prinzip ist aber 
übertragbar.

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.