Übergabe Funktionspointer und Struct

Persönliche Seite #3831964
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.
Persönliche Seite #3832014
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).
#3832020
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.
Gast #3832024
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
}
#3832038
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.
Gast #3832049
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?
#3832057
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? ;)
Gast #3832063
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...
Persönliche Seite #3832151
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.
#3832225
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 ;)
Gast #3832250
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...
Gast #3832264
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren