Link zu einer sehr anschaulichen Erklärung inklusive Grafiken und Quellcode von Callback-Funktionen

Hallo,

ich habe jetzt 8 verschiedene Erklärungen zu callback-Funktionen gelesen. Ich habe bei manchen nix verstanden bei manchen Ansatzweise etwas. Aber richtig durchgestiegen bin ich noch nicht. Deshalb hier die Frage ob jemand einen Link kennt zu einer Website die es gaaanz anschaulich mit Grafiken die die verschiedenen beteiligten Funktionen im Speicher zeigen und die auch zeigen wer ruft da wenn mit welchem Parameter (mal sind es Zeiger auf andere Funktionen, mal sind es die „eigentlichen“ Paramete) zeigt?

Ich frage hier nach einem Link weil das selbst beantworten in der Ausführlichkeit die ich benötige verdammt aufwendig wäre.
Wenn jemand Spaß daran hat es so ausführlich zu versuchen natürlich gerne. Ein Nachfragespiel mit so und so oft fragen/antworten/fragen fände ich nicht so sinnvoll. Also hier die Beschreibung wie ausführlich es sein müsste:

Man kann es wahrscheinlich auch ohne Grafiken erklären aber dann muss es echt ausführlich sein und die ganzen Funktionsnamen sollten dann nicht einfach „do“ und „foo“ sein sondern möglichst selbsterklärend. Die Erklärung müsste dann auch umfassen wieso es funktioniert ganz verschiedenartige Funktionen die auch verschiedenartige Ergebnisse zurückgeben aufzurufen. Und das alles dann an Beispielen deren Inhalt bis ins Detail bekannt sind weil der jeweilige Quellcode mit dazu geschrieben wird.

Ihr seht das ist schon verdammt hohe Erklärqualität und Ausführlichkeit. Daher bescheide ich mich lieber und Frage nach Links zu schon fertigen Erklärungen.

Vielen Dank schon im Voraus
viele Grüße

Stefan

Hallo!

Grundsätzlich sind Callback-Funktionen gar nicht so kompliziert.
Allerdings gibt es ein paar feine Unterschiede zwischen den verschiedenen Programmiersprachen, und daher wäre es schon ratsam, wenn du uns zumindest deine Sprache nennst.

Ich versuch’s trotzdem mal selbst:

Dieses Programm:

#include <stdlib.h>
#include <stdio.h>

void myPrinter(int n){
     printf("%2d*%2d=%3d\n", n, n, n*n);
}

void myLoop(){
    int i;

    for (i=1; i<11; i++){
        myPrinter(i);
    }
}

int main(void){
    myLoop();
    return 0;
}

gibt die ersten 10 Quadratzahlen auf eine etwas umständliche Art aus:

 1* 1=  1
 2* 2=  4
 3* 3=  9
 4* 4= 16
 5* 5= 25
 6* 6= 36
 7* 7= 49
 8* 8= 64
 9* 9= 81
10*10=100

Bisher ist da noch nichts mit Callback, das baue ich jetzt ein:

#include <stdlib.h>
#include <stdio.h>

void myPrinter(int n){
    printf("%2d*%2d=%3d\n", n, n, n*n);
}

void mySecondPrinter(int n){
    printf("%2d mal %2d gleich %3d\n", n, n, n*n);
}

void myLoop( void (*ThePrinter)(int) ){
    int i;
    for (i=1; i<11; i++){
        (*ThePrinter)(i);
    }
}

int main(void){
    myLoop(myPrinter);
    myLoop(mySecondPrinter);
    return 0;
}

Der erste, einfache Unterschied ist. daß ich eine zweite Funktion für die Ausgabe erstellt habe, die den gleichen Übergabeparameter entgegen nimmt, die Ausgabe aber etwas anders gestaltet.

Etwas komisch ist nun die Zeile void myLoop( void (*ThePrinter)(int) ){. Diese bedeutet: Die Funktion myLoop erwartet als Übergabeparameter den Pointer zu einer Funktion, welche ihrerseits ein einzelnes INT als Übergabeparameter erwartet.

Drei Zeilen weiter wird diese Funktion, auf die der Pointer zeigt, mit i als Parameter aufgerufen.

Nun wurde anfangs die Funktion void myPrinter(int n) definiert, der Pointer auf diese ist schlicht myPrinter. Daher steht in der main dann auch myLoop(myPrinter);, wodurch diese Funktion angewiesen wird, ihrerseits myPrinter als Funktion aufzurufen.

Da die Funktion loop zwei mal aufgerufen wurde und ihrerseits mal die eine, mal die andere Funktion benutzen soll, sieht das Ergebnis so aus:

 1* 1=  1
 2* 2=  4
 3* 3=  9    
 4* 4= 16
 5* 5= 25
 6* 6= 36
 7* 7= 49
 8* 8= 64
 9* 9= 81
10*10=100
 1 mal  1 gleich   1
 2 mal  2 gleich   4
 3 mal  3 gleich   9
 4 mal  4 gleich  16
 5 mal  5 gleich  25
 6 mal  6 gleich  36
 7 mal  7 gleich  49
 8 mal  8 gleich  64
 9 mal  9 gleich  81
10 mal 10 gleich 100

Und damit sind wir eigentlich schon fertig. Die Funktion Loop bekommt eine Funktion übergeben, die sie selbst aufrufen wird, und diese nennt man Callback-Funktion.

In der Realität steckt die Funktion myLoop beispielsweise in einer Bibliothek, die du zur Verfügung gestellt bekommen hast, und an der du selbst nichts ändern kannst. Die Funktion erzeugt Daten, weiß aber nicht, was damit geschehen soll. Sie erwartet von dir also, eine Callback-Funktion bereitgestellt zu bekommen, die sie aufrufen kann, und genau ein INT erwartet.
Ganz allgemein diktiert die Funktion, was für Übergabeparameter, und in welcher Form (Wert / Pointer) deine Callback-Funktion zu erwarten hat, und das sollte in der Doku stehen.

Ein Grund, warum man das ganze überhaupt macht ist, daß die Funktion die Callback-Funktion auch mehrfach aufrufen kann. Ein häufiger Fall ist auch, daß die Funktion parallel zum eigentlichen Programm läuft, und bei Eintritt bestimmter Ereignisse dann deine Callback-Funktion aufruft. In einem Handy-Spiel könnte z.B. eine Callback-Funktion implementiert sein, die das Spiel anhält, wenn ein Anruf rein kommt. Die App teilt dann am Anfang mit, daß diese Funktion aufgerufen werden soll, wenn ein Anruf rein kommt. Das ist besser, als wenn die App jede Sekunde prüfen muß, ob grade einer anruft…


In anderen Programmiersprachen kommen dann noch andere Feinheiten ins Spiel. In C++ beispielsweise könntest du eine Methode einer Klasse als Callback-Funktion angeben, mußt aber vorher meist eine Instanz von der Klasse erzeugen.


So, das ist schon was länger geworden… Versuche das mal zu verinnerlichen, und wenn du Fragen hierzu, oder zu nem anderen Beispiel im Netz hast, dann frag bitte ganz speziell dazu. Denn häufig betreffen die Fragen auch ein ganz bestimmtes Stück Code, und nichts allgemeines.

Wenn Du 8 Erklärungen nicht verstanden hast, liegt es vermutlich daran, dass Du keine reale Anwendung dafür hast. Also dass Du entweder selber nicht intensiv programmierst, keine Funktionalität mit Callback verwendest oder das ganze Dir nur theoretisch erschließt.

Darum sag ein wenig mehr zu Deinem Hintergrund, also ob Du Programmiererfahrung hast, ob Du z.B. C sprichst und in welchem Kontext Du das erklärt haben möchtest.

So nutzt z.B. Quicksort eine Callback-Funktion, um den Vergleich vom Aufrufer ausführen zu lassen und von der eigentlichen Sortierung (intern) zu trennen.

Der Empfangs-Callback bei einem seriellen Schnittstellentreiber macht zwar das gleich, doch fühlt es sich (für einen Anfänger/Theoretiker) ganz anders an.

Hallo Stefan,

Ich verstehe dein Problem nicht wirklich?

Ein Callback-Funktion unterscheidet sich von einer „normalen“ Funktion nur dadurch, dass die Einsprungsadresse erst zur Laufzeit bekannt ist und bei der „normalen“ Funktion schon dann, wenn das Programm gelinkt wird.

Implizite „Callback“-Funktionen sind z.B. Init() und Exit() in Bibliotheken. Init() initialisiert alles möglich, bevor irgendeine Funktion der Bibliothek aufgerufen wird. Exit() räumt dann auf, bevor die Bibliothek aus dem Speicher entladen wird. Die Konventionen des Betriebssystems, welchen die Bibliotheken verwaltet, schreibe vor wie die Funktionen aussehen müssen und welche Parameter bei einem Aufruf übergeben werden. Der Aufruf dieser Funktionen geschieht durch das Betriebssystem.

Die explizite Methode ist, dass du z.B. einer Timer-Funktion des Betriebssystems, die Adresse deiner Funktion übergibst, welche beim Ablauf des Timers aufgerufen werden soll. Meistens verwendet man dies mit periodischen Timern. Aber auch hier schreibt dir das Betriebssystem vor, welche Parameter es beim Aufruf der Callback-Funktion, übergibt. De5r Name der Funktion und die Namen der Parameter sind dabei egal, da nur die Einsprungs-Adresse übergeben wird.

Du kannst aber eine Callback-Funktion, wie jede andere Funktion deines Programms auch selbst aufrufen, die Callback-Funktion merkt dabei keinen Unterschied!

Programmablauftechnisch verhält sich eine Callback-Funktion wie eine Interrupt-Routine. Sie wird zeitlich, für dein Programm nicht vorhersehbar, zu irgendwelchen Zeitpunkten aufgerufen.

Mehr ist da nicht dahinter!

MfG Peter(TOO)

Hallo,

erst mal vielen Dank für eure Antworten.
Mein bisheriges Wissen und zu in welchem Zusammenhang ich das wissen möchte.
Ich kann ganz gut in Pascal und Delphi programmieren und ein bisschen Assembler (good old 6502, und PASM)

Die Frage nach den Callback-Funktionen entstand bei der Beschäftigung mit einem IoT-Epxerimentierboard mit dem ESP8266-Chip.
Da gibt es eine Firmware namens nodeMCU die eine Implementierung von eLua auf dem ESP8266 darstellt.
Dafür möchte ich einen ultra-pico-Webserver als Web-GUI programmieren.
Es gibt im Web natürlich verschiedene Beispiele aber eben ohne große Erklärung dazu
In der Dolumentaion zu nodeMCU gibt es nur kurze Erklärungen

Zum Beispiel die hier
https://nodemcu.readthedocs.io/en/master/en/modules/net/#netcreateserver

net.server:listen()
Listen on port from IP address.

Syntax
net.server.listen([port],[ip],function(net.socket))

Parameters
port port number, can be omitted (random port will be chosen)
ip IP address string, can be omitted
function(net.socket) callback function, pass to caller function as param if a connection is created successfully
Returns
nil

Example
– server listens on 80, if data received, print data to console and send „hello world“ back to caller
– 30s time out for a inactive client
sv = net.createServer(net.TCP, 30)

function receiver(sck, data)
print(data)
sck:close()
end

if sv then
sv:listen(80, function(conn)
conn:on(„receive“, receiver)
conn:send(„hello world“)
end)
end

Naja und bei der kurzen Erklärung und dem NICHT-übereinstimmen von Bezeichnern macht es bei nur nur häää???

Optimal verstehen würde ich das vermutlich dann wenn mir jemand eine Handzeichnung macht hier im Speicher ist dein Code A,
hier im Speicher ist dein Code B und hier im Speicher ist die call-back-funktion von X und hier die callback-funktion von Y
Wahrscheinlich macht es Sinn den Speicherplätzen auch Adressen zu geben. Nur irgendwelche damit sie unterscheidbar sind

Dann eine Erklärung an dieser Stelle steht der Befehl abc mit dem Parametern U,v,w.
U enthält den Wert … und v den Wert … das bedeutet dein Code springt jetzt nach … um … zu machen.
usw.

Eine solche Erklärung kann ich mir aus dem gegebenen Code nicht selbst erarbeiten. Es macht einfach nur häää?
Vielleicht können mir die Durchblicker jetzt weiterhelfen und ihre Erklörung an diesen Stil anpassen.

Vielen herzlichen Dank schon im Voraus

mit freundlichen Grüßen

Stefan