Hallo,
folgende Frage:
Ich habe einen Kommunikationslayer, der direkt über der Uart angesiedelt
ist. Nun kann es sein, dass Daten über die eine oder die andere Uart
ankommen. Ich möchte beim Initialisieren des Kommunikationslayers die zu
verwendenden Schreibe- u. Lesefunktion der jeweiligen Uart als Parameter
mitgeben, weiß aber nicht so recht, wie die Parameter der
Initialisierungsfunktion aussehen müssen.
Momentan ist es fest codiert:
1
void(*kiss_putc)(unsignedchar)=&uarte_putc;
2
unsignedint(*kiss_getc)(void)=&uarte_getc;
3
4
voidkiss_init()
5
{
6
7
}
Wie muss der Kopf der Initialisierungsfunktion aussehen?
Grüße, Alex
Übersichtlicher wird es, wenn man für die Typen typedefs einführt.
Die "&" in den Initialisierungen sind überflüssig; das bloße Nennen
eines Funktionsnamens (ohne runde Klammern danach) ergibt automatisch
einen Zeiger auf die Funktion.
Achso, du willst das in globalen Variablen hinterlegen … ja, sollte
klappen.
Das sind so Dinge, wo einem natürlich sofort C++ in den Sinn kommt: du
legst dein Kommunikationsobjekt an, und im Konstruktor übergibst du dem
Objekt die Zugriffsfunktionen. Sie werden in Objektattributen
gespeichert und stehen dann später der Instanz zur Verfügung.
Wenn du ein zweites gleichartiges Objekt für eine andere UART brauchst,
kannst du das dann völlig parallel einrichten. Das geht bei globalen
Variablen nicht.
Da ich auch in C# programmiere, wäre objektorientiert natürlich super.
Geht das unter gcc und Atmel Studio? Als ich meinen ersten Atmel
programmiert habe, gab's das noch nicht, meine ich.
Alexander H. schrieb:> Als ich meinen ersten Atmel programmiert habe, gab's das noch nicht,> meine ich.
C++ an sich geht schon sehr lange, nur eine libstdc++ gibt es nicht.
Das kompiliert auch unter C++11, C++14, C++17, C++20, nur falls da
jemand gesteigerten Wert drauf legen würde. Ist allerdings alleine
deshalb weder sinnvoll noch erforderlich. Die Vorteile von einem in C++
möglichen Kommunikationsobjekt haben mit dem syntaktischen Zucker eines
"using" nichts zu tun.
Oliver
Oliver S. schrieb:> syntaktischen Zucker> typedef void (*PutcPtr)(unsigned char);
Wenn hier jetzt void und unsigned char auch noch selbst definierte Typen
wären, dann ist es (was wird denn hier überhaupt definiert?) schon
schwieriger aufzulösen.
> using PutcPtr = void (*)(unsigned char);
Hier ist viel klarer/offensichtlicher, welches Token definiert wird.
Wenn es auch nur "Zucker" ist.
Auf dem Kuchen mag ich es.
Danke für die Anregungen. Momentan gibt es nur einen Layer, der entweder
von der einen oder der anderen Uart Daten bekommt und es steht auch
schon viel im aktuellen Projekt. Aber beim Nächsten könnte man sich mit
C++ mal beschäftigen.
Grüße, Alex
Arduino Fanboy D. schrieb:> Hier mal ein Vorschlag in C++11
Das ist kein C++, sondern C mit einer anderen Syntax ("using" statt
"typedef").
Wenn schon C++, dann wie oben skizziert, keine globalen Variablen mehr,
sondern Objektattribute, die vom Konstruktor intiialisiert werden (und
danach unveränderlich sind).
Arduino Fanboy D. schrieb:> Ein Birnenbaum ist ein Apfelbaum mit anderen Früchten dran.
Du hast dem Apfelbaum allerdings nur einen Birnbaumanstrich gegeben. Er
produziert weiterhin fleißig Äpfel.
Der wesentliche Punkt von C++ ist nicht, dass es eine andere (erweiterte
– "typedef" würde ja trotzdem noch genauso funktionieren) Syntax als C
hat, sondern dass man damit objektorientiert programmieren kann. Wenn
man das nicht vorhat, dann kann man auch gleich bei C bleiben, dann gibt
es nur wenige Gründe für C++. (Echte Konstanten wären vielleicht noch
einer, denn die kann man bpsw. zur Festlegung von Array-Dimensionen
benutzen, was in C nicht geht.) Ob ich nun "using" oder "typedef"
schreibe, spielt doch ansonsten wirklich in diesem Zusammenhang
überhaupt keine Geige.
Jörg W. schrieb:> sondern dass man damit objektorientiert programmieren kann.
;-) Danke, für diese wichtige Information. ;-)
Die Aussage erscheint mir viel sinnvoller und logischer als:
> Das ist kein C++, sondern C mit einer anderen Syntax
C++ ist eine Multiparadigmen Sprache.
(ist das das richtige Wort?)
> strukturiert, generisch, imperativ,> prozedural, objektorientiert, funktional
(habe ich noch was vergessen?)
Soviel, zu Äpfeln, Kirschen und Birnen.
Natürlich darfst du das prozedurale Paradigma dem C++ absprechen, oder
C++ auf OOP reduzieren....
Aber Sinn macht das nicht.
Wenn es von beiden immer nur eine Instanz gibt und das sich zur
Laufzeit nicht ändert dann linke ich solche Sachen statisch.
Zum Beispiel erwartet meine plattformunabhängige iolink.c daß irgendwo
eine handvoll Funktionen iolink_uart_****() existieren die es aufrufen
kann, sie kennt aber nur deren Prototypen, implementieren tut sie sie
selber nicht.
Je nach verwendetem Controller, UART oder IO-Link-Transceiver linke ich
dann einen von meinen hardwarespezifischen Treibern iolink-uart-xxxx.c
hinzu und diese implementieren jeweils genau diese Funktionen. Der
Linker fügt es automatisch zusammen.
Umgekehrt rufen alle meine iolink-uart-xxxx.c Dateien alle eine Handvoll
dort nicht existierender iolink_uart_hook_****() auf (sie kennen auch
nur die Prototypen) aber da gleichzeitig immer auch die iolink.c
mitgelinkt wird in der diese hooks alle implementiert sind kann der
Linker das auflösen.
Für Hook-Funktionen deren Verwendung optional ist baue ich zusätzlich
noch eine WEAK Implementation ein die nichts tut und wenn der Anwender
beschließt diese Hook-Funktion zu implementieren dann wird eben diese
gelinkt anstelle des weak defaults, in etwa so wie bei den
Interrupt-Handlern.
Die Prototypen für diese "externen" Funktionen kommen alle in den Header
des Moduls das sie aufrufen will ganz unten gesondert dokumentiert mit
dem Hinweis daß ein anderes Modul diese implementieren muss oder
implementieren kann. So lässt sich jedes Modul getrennt für sich immer
fehlerfrei kompilieren.
Wenn man sich bei den Namensgebung solcher Funktionen an ein striktes
Schema hält sieht man auf den ersten Blick wer die Funktion aufruft
und/oder in welcher Datei sie implementiert ist.
Die meisten Sachen kann ich so erschlagen. So hab ich zum Beispiel einen
USB-Treiber der würde eine usb_hook_led(bool on) aufrufen wenn irgendwo
im Projekt eine solche existiert und wenn nicht dann blinkt eben nichts
und der Compiler generiert keine einzige Instruktion. Der gcc kann sowas
hervorragend über Modulgrenzen hinweg(!) inlinen und optimieren, also
kann man großzügig mit sowas umgehen bei null Laufzeitoverhead und man
erspart sich einen Haufen Boilerplate, hässliche
Funktionszeiger-Typdefs, Überprüfung und Behandlung von Nullpointern und
sonstiges mühselige Geschreibsel.
Nur wenn es die Natur der Sache erfordert daß man mehrere Instanzen der
selben Klasse gleichzeitig haben könnte muss man zur Laufzeit binden.
Bernd K. schrieb:> Wenn es von beiden immer nur eine Instanz gibt und das sich zur> Laufzeit nicht ändert dann linke ich solche Sachen statisch.>> Zum Beispiel erwartet meine plattformunabhängige iolink.c daß irgendwo> eine handvoll Funktionen iolink_uart_****() existieren die es aufrufen> kann, sie kennt aber nur deren Prototypen, implementieren tut sie sie> selber nicht.>> Je nach verwendetem Controller, UART oder IO-Link-Transceiver linke ich> dann einen von meinen hardwarespezifischen Treibern iolink-uart-xxxx.c> hinzu und diese implementieren jeweils genau diese Funktionen. Der> Linker fügt es automatisch zusammen.
Das kann auch optimierungstechnisch besser sein als indirekte Aufrufe
via Funktionszeiger: Wenn mit LTO o.a. optimiert wird, ermöglicht das
Inlining über Modulgrenzen hinweg.
Mit Funktionszeigern ist diese Optimierung i.d.R. nicht möglich, weil
der Wert so eines des Zeigers wesentlich schwerer zu eruieren ist als
ein Symbol.
Allerdings könnte weak hier dem Compiler / LTO reingrätschen und solche
Optimierungen verhindern, müsste man sich mal genau anschauen.