Hallo zusammen,
ich versuche gerade, etwas Struktur in meinen Code und Buildprozess zu
bekommen. Dafür möchte ich allgemeine Funktionen von
Versionsspezifischen trennen.
Allgemeine Funktionen sind z.B. UART oder ADC Treiber. Diese können
"instantiiert" werden (wir sind in C, aber es gibt struct Zeiger, die
als "this" Zeiger verwendet werden).
Die Pins der UART bswp. unterscheiden sich aber, ebenso Versionsnummern
oder Identifikationsstrings, Clock Inits, einige defines usw. Das sind
versionsspezifische oder von mir aus auch produktspezifische
Einstellungen.
Ich verwende momentan Keil uVision und habe da mehrere Build options
angelegt. Diese unterscheiden sich teilweise durch ein/ausgeschaltete
Features per Build defines und der verwendeten MCU (STM32F103 und
STM32F100).
Mein Fernziel ist, einen IDE unabhängigen Build Prozess zu erstellen,
aber bis dahin ist noch ein weiter Weg.
Erstmal geht es um eine ganz konkrete Frage/Problem. Ich möchte eine Art
Interface erstellen, aber mit Implementation. Nur die Daten kommen aus
einer versionsabhängigen C Datei.
Konkrete Ausgangslage:
Dann gibt es noch weitere "maintenance_configuration2.c" und ein paar
mehr.
Durch die build options der IDE wird im Moment sichergestellt, dass
lediglich eine der configurations files compiliert und gelinkt wird.
Ich möchte nun allgemeine Funktionen, wie z.B. GetDeviceName (die
Funktion ist in Wirklichkeit etwas komplizierter) bereits im "Interface"
implementieren, aber Daten aus der c. Datei verwenden. Also andersrum
als sonst üblich.
Momentan funktioniert das, indem ich die Funktion als inline im header
implementiere und die Daten per extern deklariere:
1
// maintenance.h
2
extern const char deviceName[];
3
4
__inline void GetDeviceName(str, len)
5
{
6
*str = deviceName;
7
*len = strlen(deviceName);
8
}
1
// maintenanc_configuration1.c
2
#include maintenance.h
3
4
const char deviceName[] = "myDevicename1";
5
6
extern void GetDeviceName(str, len)
Inline deswegen, weil ich hoffe, dass damit die konstanten Werte zur
Compilezeit ersetzt werden und mir das gleichzeitig die mehrfache
identische (!) Implementation in den .c Dateien erspare.
Nachteil ist, dass durch das extern eigentlich die Daten jetzt eben auch
external linkage haben, man aber nicht direkt darauf zugreifen soll.
Kann man das so machen? Ist das sinnvoll? Wie würde man das sonst
machen?
Meine Ziele
- weg von großen ifdef Orgien
- spezifizieren der configuration durch hinzulinken der entsprechenden c
file (oder lauer ich hier dem Trugschluss auf, dass das später die
einfachste Variante in einer makefile ist?)
- keine Mehrfachimplementierung identischer Funktionalität, nur weil
sich Daten ändern
- effiziente Implementierung, z.B. soll bei den Konstanten zur
Compilezeit einfach der Wert ersetzt werden.
Vielen Dank für jeglichen Input!
Jede .c wird für sich compiliert. Daten in einer .c ersetzen nichts in
einer anderen .c.
Wenn Du in my.c XYZ durch "device 7" ersetzt haben möchtest, dann
entweder in einer Header oder im makefile (was aber nicht über 1
hinausgehen sollte).
Du kannst z.b. je Plattform ein anderes Header Verzeichnis einbinden
oder je Plattform ein #define oder ein #if verwenden. Entsprechend
hierarchisch kannst Du mit 1 insgesamt (je Plattform) für alle sourcen
auskommen, wenn das Dein Ziel ist. #if-Orgien müssen nicht sein, sind
aber manchmal lesbarer als Zwiebelschichten und indirekt.
Wenn's nur 1 Device im Projekt gibt zur "Maintenance" könnte man die
Konfiguration, inklusive Code, in eine Header-Datei auslagern und per
command-line define includieren
maint_if.h
Vielen Dank für deine Antwort.
Mir ist bewusst, dass jede translation Unit erstmal für sich alleine
steht. Daher ja die getter.
Die Grundidee ist: ein generisches config Interface, als zentrale Datei.
Alle darin enthaltenen Funktionen müssen implementiert werden.
Identische Funktionen können und sollten eben dort aber auch schon
implementiert sein. Daten kommen aus der “Spezialisierung”, die bedingt
kompiliert werden.
Deswegen die Idee mit dem Header und der inline Funktionen.
Die Hoffnung war, dass durch lto und co kein overhead entsteht. Genauso,
als würde ich einfach mit globalen defines arbeiten, z.B. Für den
deviceName.
Hi,
vielen Dank für den Code. Ich habe aber das Gefühl, dass es eher
komplexer als einfacher wird.
Es gibt nur ein config zur Compilezeit. Im obigen Fall ein device. Die
configs sind alles Konstanten, daher würde ich gerne zur Laufzeit
erstellte structs o.ä. vermeiden, falls diese nicht wegoptimiert werden.
Oder hab ich was missverstanden?
JK schrieb:> Genauso, als würde ich einfach mit globalen defines arbeiten, z.B. Für> den deviceName.
Hast Du Dich schon Mal mit Funktionspointer beschäftigt? Einen Großteil
Deiner Aufgaben lösen die (fast) ohne Overhead und vor allem mit
optionaler Implementierung (also kommt z.b. "No device" zurück, wenn das
Modul den getter noch nicht implementiert hat.
Ja, aber aber die sind noch schwieriger zu optimieren und zu inlinen.
Die oben genannte deviceId und der deviceName werden häufig in anderen
Modulen benötigt und sind Konstanten. Also wäre der ideale Fall, dass
sie auch zur Compilezeit als solche ersetzt werden.
Ich setze aber ein Testprojekt auf.
Ich verstehe ja nicht, warum man einerseitz schön dynamisch configs in
.c Dateien zur link- & run-time configuration haben will, aber
andererseits alles möglichst inlinen und und absurd zur compiletime
vor-optimieren will. Entweder man geht den Weg, oder eben halt nicht,
man kann nicht beides haben.
JK schrieb:> spezifizieren der configuration durch hinzulinken der entsprechenden c> file (oder lauer ich hier dem Trugschluss auf, dass das später die> einfachste Variante in einer makefile ist?)
Ich denke, ob du ne Liste mit Files, oder ne Liste mit Optionen änderst,
ist make ziemlich egal. Und wie man die Konfiguration dann umsetzt und
organisiert, da sind der Fantasie auch keine Grenzen gesetzt. Die Frage
ist eher, wie willst du, dass die Konfiguration nachher aussieht.
Linux und U-Boot verwenden Kconfig, aber da gibt es nicht wirklich eine
offizielles standalone variante davon, und es ist GPL (wie funktioniert
das Lizenztechnisch überhaupt bei U-Boot?).
Man könnte auch makefiles für diverse Componenten machen, eins welches
die Komponenten für eine Version in ner variable auflistet, und dann
eines das dann die makefiles der restlichen komponenten einbindet, und
weiss, wie man die baut. etc.
Oder man macht was anderes. Überleg die nicht, wie du das umsetzen
willst, überleg dir, wie das nachher verwendet werden soll, und überleg
dir danach, wie man da das umsetzen kann.
JK schrieb:> Es gibt nur ein config zur Compilezeit.
Dann passt die erste Variante: Je Device braucht du nur die DEFINEs im
maint_configX.h zu setzen und dem Kompiler über die -D Option
mitzuteilen welche Headerdatei includiert werden soll.
Eric B. schrieb:> #include MAINT_CONFIG
...
> compile:gcc -o maintenance.o -DMAINT_CONFIG="""main_config1.h"""> maintenance.c(ja, dreifach " auf der Windows cmd-line)
Oder
1
#define xstr(s) str(s)
2
#define str(s) #s
3
#include xstr(MAINT_CONFIG)
und dann geht das compilieren mit -DMAINT_CONFIG=main_config1.h, ohne
allen Gänsefüsschen.
Vielleicht machst Du mal ein Beispiel, wo Du Probleme siehst. Ansonsten
stimme ich DPA zu
DPA schrieb:> Ich denke, ob du ne Liste mit Files, oder ne Liste mit Optionen änderst,> ist make ziemlich egal. Und wie man die Konfiguration dann umsetzt und> organisiert, da sind der Fantasie auch keine Grenzen gesetzt. Die Frage> ist eher, wie willst du, dass die Konfiguration nachher aussieht.
Direkt straight wäre je Plattform eine Header (und ggf. ein C-File) und
gut ist. C können C-Programmierer und IDEs lesen, Dein Make nicht
unbedingt, wenn es abgefahren wird.
Ob Du nun alle Header einbindest und den sowieso vorhanden include-Guard
erweiterst, oder ob Du in einer Header die richtige per #ifdef wählst
oder ob Du die richtige Einkommentierst, ist am Ende Egal. Auch der
Trick mit der Ersetzung geht, aber da verwirrst Du schon den ersten
Maintainer, wenn er die include-Datei gar nicht findet im Quelltext.
Irgendwo musst Du die 1-aus-n Auswahl treffen, die Du builden willst,
dass kann gerne im Makefile sein.
DPA schrieb:> Ich verstehe ja nicht, warum man einerseitz schön dynamisch> configs in> .c Dateien zur link- & run-time configuration haben will, aber> andererseits alles möglichst inlinen und und absurd zur compiletime> vor-optimieren will. Entweder man geht den Weg, oder eben halt nicht,> man kann nicht beides haben.
Da es um „compiletime-dynamische“ Daten geht, kann und will man das.
Oliver