Forum: Compiler & IDEs Bauen von Libs - Anhängigkeiten


von Reddy (Gast)


Lesenswert?

Hallo!

Ich habe eine Frage bzgl. statischen Libs und Abhängigkeiten in C:
Wie löse ich folgendes Problem am geschicktesten:
Ich habe ein Modul X (bestehend aus mehreren C-Dateien), dass ich gerne 
einmal als statische Lib kompilieren und später in meinen 
unterschiedlichen Hauptprojekten einsetzen möchte.
Das Problem ist, dass das Modul X auf unterlagerten Funktionen zugreift, 
die aber bei den Projekten unterschiedlich ausgeprägt sind.

Wie gestalte ich nun diese Schnittstelle am Besten?
Dachte an Registrierung von Callbacks. Aber es ist doch schon 
ungewöhnlich, dass sich eine untere Schicht an der darüberliegenden 
"registriert".
Zudem finde ich es umständlich immer auf != NULL zu prüfen, bevor ich 
die Adresse anspringe ...

Danke.

VG
Reddy

von Joerg W. (joergwolfram)


Lesenswert?

Auch wenn es nicht exakt das gleiche Problem ist, ich mache so etwas 
über Funktionspointer. Hauptsächlich dann, wenn eine "Higlevel-Funktion" 
auf eine "Lowlevel-Funktion" aufsetzt und ich bei letzterer mehrere 
Möglichkeiten habe.

Z.B. in einem Modul für SPI Flashes gibt es ein:
1
extern unsigned char M25P16_SPI_COMM(unsigned char);

welches die generische SPI-Routine ist. Dann "initialisiere" ich das im 
Hauptprogramm mit:
1
unsigned char M25P16_SPI_COMM(unsigned char x)
2
{
3
    return comm_spi0(x);
4
}
wobei das meist auch wieder auf Routinen innerhalb der Lib zeigt. Damit 
kann ich zu Debug-Zwecken auch mal schnell auf die serielle 
Schnittstelle des Controllers umleiten.
Sollte ich die Initialisierung im Hauptprogramm "vergessen", gibt es 
einen Fehler beim Linken.

Jörg

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

Für unterschiedliche Platformen:
- gar nicht

Für die selbe Platform:
- bei Laufzeit-Abhängigkeiten -> Callbacks
- ohne Laufzeit-Abhängigkeiten -> Funktionsdeklarationen

@joergwolfram
Eine Funktionsdeklaration ist kein Funktionspointer.

von Dr. Sommer (Gast)


Lesenswert?


von Reddy (Gast)


Lesenswert?

Hallo!

Danke für die Antworten!

Joerg W. schrieb:
> Auch wenn es nicht exakt das gleiche Problem ist, ich mache so etwas
> über Funktionspointer.
Funktionspointer vs. Callbacks?!?

> extern unsigned char M25P16_SPI_COMM(unsigned char);
Wie!?
Reicht es aus, dass ich in der zu erstellenden Lib das Headerfile von 
der unterlagerten Schicht inkludiere und dort die Funktionen als extern 
deklariere? Habe ich das nicht so verstanden, dass das implizit immer 
eine Deklaration ist?
siehe: Beitrag "Re: Den Befehl extern"

Vincent H. schrieb:
> Für die selbe Platform:
Sicher für die selbe Paltform. ich möchte ja, wie geschrieben meine Lib 
dazu linken.
> - bei Laufzeit-Abhängigkeiten -> Callbacks
Mein Vorschlag.
> - ohne Laufzeit-Abhängigkeiten -> Funktionsdeklarationen
Das ist die Lösung von Joerg W., oder?

Dr. Sommer schrieb:
> https://en.wikipedia.org/wiki/Inversion_of_control
> https://en.wikipedia.org/wiki/Multitier_architecture
> https://en.wikipedia.org/wiki/Dependency_injection
> https://en.wikipedia.org/wiki/Strategy_pattern
> https://en.wikipedia.org/wiki/Observer_pattern

uff
Viel. Und auf English schauder.
Werde es mir aber mal anschauen. Danke.

VG
Reddy

von Dr. Sommer (Gast)


Lesenswert?

Reddy schrieb:
> Reicht es aus, dass ich in der zu erstellenden Lib das Headerfile von
> der unterlagerten Schicht inkludiere und dort die Funktionen als extern
> deklariere?
Ja. Funktionen werden erst beim Linken aufgelöst; ob Nutzung und 
Definition der Funktion jetzt in einer statischen Library oder "direkt" 
genutzten .o -Datei vorkommt ist egal. Statische Libraries (.a) sind 
sowieso nur Archive an .o-Dateien. "extern" ist bei 
Funktions-Deklarationen übrigens komplett überflüssig und macht 
keinerlei Unterschied.

Reddy schrieb:
> Viel. Und auf English schauder.
Informatik ist nicht umsonst ein Studiengang :-) Über solche Probleme 
wurde schon viel nachgedacht und geforscht. Da gibt es viel zu lesen, 
auch deutsche Literatur. Auf Englisch findet man halt mehr. UML kann man 
sich bei der Gelegenheit auch mal ansehen.

von Joerg W. (joergwolfram)


Lesenswert?

@Vincent H.

Ja, da ist mir ein Fehler unterlaufen und ich hab das falsche Beispiel 
genommen.

Bei den SPI/I2C... Routinen habe ich irgendwann die Funktionspointer 
wieder rausgeschmissen, da ich das während der Laufzeit nie ummappen 
muss. Hier noch das Beispiel für meine printf Ausgaberoutine:
1
void (*printf_output) (unsigned char);
2
3
//#######################################################################
4
//#  set printf channel
5
//#######################################################################
6
void set_outchar_routine(void (*outfunc)(unsigned char))
7
{
8
  printf_output = outfunc;
9
}
10
11
void printf_outchar(unsigned char c)
12
{
13
  (*printf_output) (c);
14
}
15
16
//#######################################################################
17
//#  dummy output 
18
//#######################################################################
19
void OUTPUT_NONE(unsigned char c)
20
{
21
  asm volatile("se_or  2,2");
22
}

Bei der Initialisierung wird der Pointer mit OUTPUT_NONE (in diesem Fall 
PowerPC) vorbelegt.

von Vincent H. (vinci)


Lesenswert?

Reddy schrieb:
> Hallo!
>
> Danke für die Antworten!
>
> Joerg W. schrieb:
>> Auch wenn es nicht exakt das gleiche Problem ist, ich mache so etwas
>> über Funktionspointer.
> Funktionspointer vs. Callbacks?!?

In C sind Callbacks immer Funktionspointer ja. Ersteres ist einfach nur 
ein "Architekturbegriff" und zweiteres die Implementierung davon.


>> extern unsigned char M25P16_SPI_COMM(unsigned char);
> Wie!?
> Reicht es aus, dass ich in der zu erstellenden Lib das Headerfile von
> der unterlagerten Schicht inkludiere und dort die Funktionen als extern
> deklariere? Habe ich das nicht so verstanden, dass das implizit immer
> eine Deklaration ist?
> siehe: Beitrag "Re: Den Befehl extern"

Prinzipiell ja, es ist jedoch bad practice "business logic" (sprich 
Header deiner anderen Projekte) in die Bibliothek einzubinden. Genau 
dadurch entstehen Abhängigkeiten zwischen Projekt und Bibliothek die du 
nicht haben willst. Um genau diese Probleme kümmert sich auch ein 
Großteil der von Dr.Sommer geposteten "Patterns", die eben Vorschläge 
beinhalten um jene Abhängigkeiten ausschließlich in eine Richtung zu 
haben und so gering wie möglich zu halten.

In der Praxis wird das eher so gehandhabt, dass diverse Abhängikeiten in 
der Doku vermerkt werden. Dort steht dann exlizit drin "dein Programm 
muss die Funktion foo mit der Signatur void(int) beinhalten".

von Bernd K. (prof7bit)


Lesenswert?

Reddy schrieb:
> dort die Funktionen als extern
> deklariere?

Das "extern" bei einer Funktionsdeklaration kann man sich schenken denn 
es ist implizit bereits enthalten, es hat also keine zusätzliche 
Wirkung wenn man es nochmal explizit hinschreibt.
1
extern void foo(void);

und
1
void foo(void);

sind hundertprozentig das selbe.

von Bernd K. (prof7bit)


Lesenswert?

Vincent H. schrieb:
> In der Praxis wird das eher so gehandhabt, dass diverse Abhängikeiten in
> der Doku vermerkt werden. Dort steht dann exlizit drin "dein Programm
> muss die Funktion foo mit der Signatur void(int) beinhalten".

Das ist auch nichts ungewöhnliches, jeder kennt das. So muß zum Beispiel 
jedes Programm welches die übliche standard library hinzulinkt eine 
funktion mit der Signatur int main(void); enthalten sonst meckert der 
Linker.

Man macht das dann einfach ohne groß drüber nachzudenken aber irgendwo 
in den Tiefen der standardlibrary in irgendeinem header dort ist int 
main(void); deklariert und die wird dann irgendwo aufgerufen und der 
Linker sucht dann alles ab ob er irgendwo die Implementierung der 
besagten main findet.

Auch zum Beispiel in der STM32 HAL gibt es solche hook-Funktionen mit 
denen man sich zum Beispiel in die Interrupthandler der HAL reinhängen 
kann.

Genauso kann man das mit eigenen Libs machen, man muß dann halt an 
prominenter Stelle dokumentieren welche "hook"-Funktionen zwingend 
implementiert werden müssen und was die machen sollen. Wenn man sich 
dann noch ein durchgängiges Namensschema ausdenkt, zum Beispiel 
name_der_lib Unterstrich hook Unterstrich name_der_funktion um solche 
Funktionen deutlich als solche kenntlich zu machen geht das auch ganz 
gut und dokumentiert sich fast von selbst.

foo.h
1
void foo_init();
2
void foo_bar();
3
void foo_baz();
4
5
// aplication MUST implement the following hook functions
6
void foo_hook_rx(foo_packet_t* data);  // implement this to process incoming data
7
void foo_hook_blinkenlight(bool on);   // foo will call this hook to control (optional) blinkenlights

: Bearbeitet durch User
von Reddy (Gast)


Lesenswert?

Also wenn ich in meinem Modul X um ein Header-File erweitere, in der die 
Funktionen der darunterliegenden Schicht deklariert werden, kann ich das 
Modul als statische Lib bauen, ohne das die Funktionen zur Kompilierzeit 
vorhanden, sprich definiert sind?

In meinem Hauptprojekt erstelle ich dann die darunterliegende Schicht.
Inkludiere dort das Interface-Headerfile von meinem Modul X, definiere 
in einer c-Datei die Funktionen und befülle sie mit Leben und linke 
final die statische Lib meines Modul X hinzu?!?

von Reddy (Gast)


Lesenswert?

Joerg W. schrieb:
> //###################################################################### #
> //#  set printf channel
> //###################################################################### #
> void set_outchar_routine(void (*outfunc)(unsigned char))
> {
>   printf_output = outfunc;
> }

Hallo Jörg,

sicherheitshalber solltest du wenigstens die Adresse von outfunc auf != 
NULL prüfen, bevor du sie setzt. :-)
Die Steigerung ist, dass du sogar den Speicherbereich auf Gültigkeit 
prüfst (Also z.B. nur zwischen 0x8000000 und 0x8007FFFF, was der interne 
Speicherbereich vom Flash ist).

von Bernd K. (prof7bit)


Lesenswert?

Reddy schrieb:
> Also wenn ich in meinem Modul X um ein Header-File erweitere, in der die
> Funktionen der darunterliegenden Schicht deklariert werden, kann ich das
> Modul als statische Lib bauen, ohne das die Funktionen zur Kompilierzeit
> vorhanden, sprich definiert sind?

Zur Kompilierzeit müssen nur alle Deklarationen (Prototypen) da sein auf 
die im Code Bezug genommen wird, die eigentlichen Definitionen 
(Implementierungen) die an den betreffenden Stellen dann verwendet 
werden sollen werden erst beim Linken gesucht. Also ja.

von Joerg W. (joergwolfram)


Lesenswert?

Das mit der Prüfung auf !=0 könnte man sicherlich noch einbauen, 
allerdings fällt mir jetzt nicht ein, wie ich der Funktion 
"versehentlich" eine 0 unterjubeln kann. Vielleicht mit einem Array von 
Funktionspointern, von denen einige unbelegt sind.


Die Prüfung auf den Speicherbereich würde bei 0x8000000 und 0x8007FFFF 
nur auf diverse ARMs zutreffen, dazu noch nur auf das Flash. Gerade 
kleinere Programme kann man auch zum Testen schnell mal ins RAM schieben 
(soweit der Controller das erlaubt) und die STM32 sind für mich nur eine 
Controller-Familie unter vielen.

So habe ich aber ein generisches set_outchar_routine(), welches auf 
allen derzeit 13 unterstützten Controller-Familien gleich funktioniert.


Ich benutze fast ausschließlich nur noch eine einzige Bibliothek, die 
ich für jeden unterstützten Controller einzeln compilieren lasse 
(automatisch). Die dafür zuständige Header-Datei mit den Prototypen 
lasse ich natürlich auch automatisch erzeugen, bei derzeit teilweise 
über 1100 Funktionen ist das manuell nicht mehr zu pflegen. Das extern, 
auch wenn es eigentlich nicht gebraucht wird, verhindert so, dass 
Routinen mehrfach in das Headerfile wandern. Dazu ignoriert das Script 
einfach alle Zeilen, die mit "extern" beginnen. Und so habe ich mir das 
halt so angewöhnt...

Jörg

von Reddy (Gast)


Lesenswert?

Check!

Das mit den Header-Files funktioniert! ;)
Danke!

Joerg W. schrieb:
> Die dafür zuständige Header-Datei mit den Prototypen
> lasse ich natürlich auch automatisch erzeugen,

Wow! Und wie machst du das?

VG
Reddy

von Joerg W. (joergwolfram)


Angehängte Dateien:

Lesenswert?

Grundvoraussetzung ist eine fixe Struktur für das Bauen der 
Bibliotheken, die ich mittlerweile auch bei fast allen Projekten 
einhalte. Das ist aber erst über die Jahre so "gewachsen", die unilib 
ist sozusagen mein privater HAL.

1. Es gibt 3 Unterverzeichisse, src, inc und build
2. Standardisierte Makefiles, Includes und Linkerscripts, natürlich fast 
alles via Perl-Scripten erzeugt.
3. Im src Verzeichnis gibt es wieder jede Menge Unterverzeichnisse, für 
High-Level Funktionen sind das eigentlich nur Symlinks, da sie für alle 
µC Familien weitestgehend identisch sind.
4. Ein Perl-Script liest erst eine Datei mit diversen Definitions ein 
und arbeitet danach die ganzen Source-Dateien ab.

5. Die Quelltextdateien haben einen festen Aufbau:

Mit //# beginnt der Kommentar zu einer neuen öffentlichen Funktion. Die 
beginnt in der Zeile nach dem letzten Kommentar. Ist die Funktion in ASM 
realisiert, steht vor der Deklaration //ASM was beim Erstellen des 
Headerfiles wieder entfernt wird. Außerdem gibt es noch //private und 
//internal als Kommentare für Variablen. Das Ende einer Funktion ist 
erreicht, wenn vorne eine schließende geschweifte Klammer steht.

Ziel des Ganzen ist, die Hardware soweit zu abstrahieren, dass Programme 
praktisch ohne Änderungen auf unterschiedlichen Controller-Familien 
laufen können. Dabei gibt es natürlich Einschränkungen, denn es werden 
nur Grundfunktionen unterstützt. Aber damit komme ich bei den meisten 
Projekten bereits aus. Zusammen mit meinem Universalprogrammer kann ich 
mich dann auf die Entwicklung der eigentlichen Anwendung konzentrieren, 
ein "make start" reicht aus, um das Programm zu compilieren, zu flashen 
und dann den Controller via Programmer mit Strom zu versorgen. Oder ein 
"make run", um es neu zu kompilieren, entsprechend zu linken und dann im 
RAM zu starten.

Normalerweise liegen dann die ganzen Dateien unter 
/usr/local/toolchain... , inzwischen kann ich aber auch (automatisiert) 
portable Rumpf-Projekte generieren, bei denen nur noch der Pfad zur 
Toolchain ins Makefile eingetragen werden muss. Bei Bedarf kann ich 
gerne mal so ein Rumpf-Projekt hochladen.

von Daniel A. (daniel-a)


Lesenswert?

Mit weak symbols und dem constructor attribut kann man auch einige 
interessante Dinge anstellen: 
https://www.mikrocontroller.net/articles/C_Linkerhacks_%2B_Modularisierung

von Johannes S. (Gast)


Lesenswert?

Vor allem interessante Fehler, zB wenn man in einem C Modul die Funktion 
aufruft und die in einem C++ Modul definiert (ohne extern C).
STM HAL nutzt das reichlich, ich würde das eher vermeiden.

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.