Was ist ein “Callback” in C und wie werden sie implementiert?

Lesezeit: 11 Minuten

Benutzeravatar von noizetoys
Geräuschspielzeug

Nach meiner Lektüre stützt sich Core Audio stark auf Callbacks (und C++, aber das ist eine andere Geschichte).

Ich verstehe das Konzept (irgendwie), eine Funktion einzurichten, die wiederholt von einer anderen Funktion aufgerufen wird, um eine Aufgabe zu erfüllen. Ich verstehe nur nicht, wie sie eingerichtet werden und wie sie tatsächlich funktionieren. Irgendwelche Beispiele würden geschätzt.

Benutzeravatar von aib
aib

Es gibt keinen “Callback” in C – nicht mehr als jedes andere generische Programmierkonzept.

Sie werden mithilfe von Funktionszeigern implementiert. Hier ist ein Beispiel:

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}

int getNextRandomValue(void)
{
    return rand();
}

int main(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);
    ...
}

Hier die populate_array Die Funktion nimmt einen Funktionszeiger als dritten Parameter und ruft ihn auf, um die Werte zu erhalten, mit denen das Array gefüllt werden soll. Wir haben den Rückruf geschrieben getNextRandomValuedie einen zufälligen Wert zurückgibt und einen Zeiger darauf übergibt populate_array. populate_array ruft unsere Callback-Funktion zehnmal auf und weist die zurückgegebenen Werte den Elementen im angegebenen Array zu.

  • Ich kann mich hier irren, aber sollte die Zeile in populate_array, die den Funktionszeiger aufruft, nicht sein: array[i] = (*getNextValue)(); ?

    – Nathan Fellmann

    27. September 2008 um 20:02 Uhr

  • Der Dereferenzierungsoperator ist bei Funktionszeigern optional, ebenso wie der addressof-Operator. meinefunktion(…) = (*meinefunktion)(…) und &meinefunktion = meinefunktion

    – Aib

    30. September 2008 um 14:14 Uhr

  • @NathanFellman Ich habe gerade gelesen Experte C-Programmierung und es erklärt den Funktionszeiger, der gut aufruft.

    – Matt Clarkson

    29. November 2011 um 14:12 Uhr

  • @johnny Weil der Standard es so sagt. Sehen Sie sich den positiven Kommentar an.

    – Aib

    7. Mai 2014 um 8:41 Uhr

  • @Patrick: populateArray befindet sich in einer Bibliothek (und wurde vor 12 Jahren geschrieben) und Sie haben getNextRandomValue selbst geschrieben (gestern); es kann es also nicht direkt aufrufen. Denken Sie an eine Bibliotheks-Sortierfunktion, der Sie den Komparator selbst zur Verfügung stellen.

    – Aib

    13. Juni 2016 um 11:37 Uhr

Hier ist ein Beispiel für Callbacks in C.

Angenommen, Sie möchten Code schreiben, der es ermöglicht, Callbacks zu registrieren, die aufgerufen werden, wenn ein bestimmtes Ereignis eintritt.

Definieren Sie zunächst den Funktionstyp, der für den Callback verwendet wird:

typedef void (*event_cb_t)(const struct event *evt, void *userdata);

Definieren Sie nun eine Funktion, die zum Registrieren eines Rückrufs verwendet wird:

int event_cb_register(event_cb_t cb, void *userdata);

So würde Code aussehen, der einen Callback registriert:

static void my_event_cb(const struct event *evt, void *data)
{
    /* do stuff and things with the event */
}

...
   event_cb_register(my_event_cb, &my_custom_data);
...

In den Interna des Event-Dispatchers kann der Callback in einer Struktur gespeichert werden, die etwa so aussieht:

struct event_cb {
    event_cb_t cb;
    void *data;
};

So sieht der Code aus, der einen Callback ausführt.

struct event_cb *callback;

...

/* Get the event_cb that you want to execute */

callback->cb(event, callback->data);

  • Genau das, was ich brauchte. Der Userdata-Teil ist sehr hilfreich, wenn Ihre Benutzer benutzerdefinierte Daten (z. B. Geräte-Handles) übergeben möchten, die in der Callback-Funktion erforderlich sind.

    – uceumern

    30. Juli 2015 um 12:59 Uhr

  • Überprüfungsfrage: Ist der Callback-Typedef mit einem Sternchen versehen, weil es ein Zeiger auf die Funktionsadresse ist? Wenn das Sternchen fehlt, wäre das falsch? Wenn das nicht stimmt, dann fehlen zwei Sterne in der libsrtp-Bibliothek von Cisco auf GitHub: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…

    – twildeman

    7. November 2017 um 10:13 Uhr


  • @twildeman Es scheint trivial zu sein, Ihre eigene Frage zu beantworten, indem Sie in einem Standard-C-Modus mit aktivierten Warnungen kompilieren. Sie können auch ein minimiertes Testprogramm schreiben. Code wie die in libsrtp gibt keine Warnungen. Ich nehme also an, dass ein solcher Typ, wenn er als Funktionsargument erscheint, zu einem Zeiger auf eine Funktion „zerfallen“ muss, genau wie Arrays zu Zeigern auf ihre ersten Elemente zerfallen, sodass am Ende dasselbe passiert so oder so. Es ist Interessant ist jedoch, dass Diskussionen über solche Typedefs, die ich gefunden habe, diesen Aspekt nicht einmal würdigen, sondern sich darauf konzentrieren, Prototypen oder Zeiger damit zu deklarieren

    – Unterstrich_d

    12. Januar 2019 um 15:46 Uhr

  • Ich habe keine Ahnung, was das bewirkt, und es kann nicht erfolgreich kompiliert werden. Kann jemand es detailliert erklären oder den Rest des Codes ausfüllen, um erfolgreich zu kompilieren?

    – Andy Lin

    9. Juni 2020 um 10:14 Uhr

Benutzeravatar von Gautham Kantharaju
Gautham Kantharaju

Ein einfaches Rückrufprogramm. Hoffe es beantwortet deine Frage.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "../../common_typedef.h"

typedef void (*call_back) (S32, S32);

void test_call_back(S32 a, S32 b)
{
    printf("In call back function, a:%d \t b:%d \n", a, b);
}

void call_callback_func(call_back back)
{
    S32 a = 5;
    S32 b = 7;

    back(a, b);
}

S32 main(S32 argc, S8 *argv[])
{
    S32 ret = SUCCESS;

    call_back back;

    back = test_call_back;

    call_callback_func(back);

    return ret;
}

Benutzeravatar von daemondave
Dämonave

Eine Callback-Funktion in C ist das Äquivalent eines Funktionsparameters / einer Variablen, die zur Verwendung in einer anderen Funktion zugewiesen wurde.Wiki-Beispiel

Im folgenden Code

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

/* The calling function takes a single callback as a parameter. */
void PrintTwoNumbers(int (*numberSource)(void)) {
    printf("%d and %d\n", numberSource(), numberSource());
}

/* A possible callback */
int overNineThousand(void) {
    return (rand() % 1000) + 9001;
}

/* Another possible callback. */
int meaningOfLife(void) {
    return 42;
}

/* Here we call PrintTwoNumbers() with three different callbacks. */
int main(void) {
    PrintTwoNumbers(&rand);
    PrintTwoNumbers(&overNineThousand);
    PrintTwoNumbers(&meaningOfLife);
    return 0;
}

Die Funktion (*numberSource) innerhalb des Funktionsaufrufs PrintTwoNumbers ist eine Funktion zum “Rückrufen” / Ausführen von innerhalb von PrintTwoNumbers, wie es der Code während der Ausführung vorschreibt.

Wenn Sie also so etwas wie eine pthread-Funktion hätten, könnten Sie eine andere Funktion zuweisen, die innerhalb der Schleife ab ihrer Instanziierung ausgeführt wird.

Benutzeravatar von Richard Chambers
Richard Kammern

Ein Callback in C ist eine Funktion, die einer anderen Funktion zur Verfügung gestellt wird, um irgendwann “zurückzurufen”, wenn die andere Funktion ihre Aufgabe erledigt.

Es gibt zwei Möglichkeiten, wie ein Rückruf verwendet wird: synchroner Rückruf und asynchroner Rückruf. Ein synchroner Rückruf wird für eine andere Funktion bereitgestellt, die eine Aufgabe ausführen und dann mit abgeschlossener Aufgabe zum Aufrufer zurückkehren wird. Ein asynchroner Rückruf wird einer anderen Funktion bereitgestellt, die eine Aufgabe startet und dann mit möglicherweise nicht abgeschlossener Aufgabe zum Aufrufer zurückkehrt.

Ein synchroner Rückruf wird normalerweise verwendet, um einen Delegaten für eine andere Funktion bereitzustellen, an die die andere Funktion einen Schritt der Aufgabe delegiert. Klassische Beispiele für diese Delegation sind die Funktionen bsearch() und qsort() aus der C-Standardbibliothek. Beide Funktionen nehmen einen Rückruf, der während der Aufgabe verwendet wird, die die Funktion bereitstellt, damit der Typ der gesuchten Daten im Fall von bsearch()oder sortiert, im Fall von qsort()muss der verwendeten Funktion nicht bekannt sein.

Hier ist zum Beispiel ein kleines Beispielprogramm mit bsearch() Verwendung verschiedener Vergleichsfunktionen, synchrone Callbacks. Indem wir uns erlauben, den Datenvergleich an eine Callback-Funktion zu delegieren, die bsearch() Mit der Funktion können wir zur Laufzeit entscheiden, welche Art von Vergleich wir verwenden möchten. Dies ist synchron, weil, wenn die bsearch() Die Funktion gibt zurück, dass die Aufgabe abgeschlossen ist.

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

typedef struct {
    int iValue;
    int kValue;
    char label[6];
} MyData;

int cmpMyData_iValue (MyData *item1, MyData *item2)
{
    if (item1->iValue < item2->iValue) return -1;
    if (item1->iValue > item2->iValue) return 1;
    return 0;
}

int cmpMyData_kValue (MyData *item1, MyData *item2)
{
    if (item1->kValue < item2->kValue) return -1;
    if (item1->kValue > item2->kValue) return 1;
    return 0;
}

int cmpMyData_label (MyData *item1, MyData *item2)
{
    return strcmp (item1->label, item2->label);
}

void bsearch_results (MyData *srch, MyData *found)
{
        if (found) {
            printf ("found - iValue = %d, kValue = %d, label = %s\n", found->iValue, found->kValue, found->label);
        } else {
            printf ("item not found, iValue = %d, kValue = %d, label = %s\n", srch->iValue, srch->kValue, srch->label);
        }
}

int main ()
{
    MyData dataList[256] = {0};

    {
        int i;
        for (i = 0; i < 20; i++) {
            dataList[i].iValue = i + 100;
            dataList[i].kValue = i + 1000;
            sprintf (dataList[i].label, "%2.2d", i + 10);
        }
    }

//  ... some code then we do a search
    {
        MyData srchItem = { 105, 1018, "13"};
        MyData *foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_iValue );

        bsearch_results (&srchItem, foundItem);

        foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_kValue );
        bsearch_results (&srchItem, foundItem);

        foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_label );
        bsearch_results (&srchItem, foundItem);
    }
}

Ein asynchroner Rückruf unterscheidet sich darin, dass die Aufgabe möglicherweise nicht abgeschlossen wird, wenn die aufgerufene Funktion, für die wir einen Rückruf bereitstellen, zurückkehrt. Diese Art von Rückruf wird häufig mit asynchroner E/A verwendet, bei der ein E/A-Vorgang gestartet und dann nach Abschluss der Rückruf aufgerufen wird.

Im folgenden Programm erstellen wir einen Socket zum Abhören von TCP-Verbindungsanforderungen, und wenn eine Anforderung empfangen wird, ruft die abhörende Funktion die bereitgestellte Callback-Funktion auf. Diese einfache Anwendung kann ausgeführt werden, indem Sie sie in einem Fenster ausführen, während Sie die verwenden telnet Dienstprogramm oder einen Webbrowser, um zu versuchen, in einem anderen Fenster eine Verbindung herzustellen.

Ich habe den größten Teil des WinSock-Codes aus dem Beispiel übernommen, das Microsoft mit dem bereitstellt accept() Funktion bei https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx

Diese Anwendung startet a listen() auf dem lokalen Host, 127.0.0.1, unter Verwendung von Port 8282, sodass Sie beide verwenden können telnet 127.0.0.1 8282 oder http://127.0.0.1:8282/.

Diese Beispielanwendung wurde als Konsolenanwendung mit Visual Studio 2017 Community Edition erstellt und verwendet die Microsoft WinSock-Version von Sockets. Für eine Linux-Anwendung müssten die WinSock-Funktionen durch die Linux-Alternativen ersetzt und die Windows-Threads-Bibliothek verwendet werden pthreads stattdessen.

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

#include <Windows.h>

// Need to link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

// function for the thread we are going to start up with _beginthreadex().
// this function/thread will create a listen server waiting for a TCP
// connection request to come into the designated port.
// _stdcall modifier required by _beginthreadex().
int _stdcall ioThread(void (*pOutput)())
{
    //----------------------
    // Initialize Winsock.
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        printf("WSAStartup failed with error: %ld\n", iResult);
        return 1;
    }
    //----------------------
    // Create a SOCKET for listening for
    // incoming connection requests.
    SOCKET ListenSocket;
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {
        wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port for the socket that is being bound.
    struct sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    service.sin_port = htons(8282);

    if (bind(ListenSocket, (SOCKADDR *)& service, sizeof(service)) == SOCKET_ERROR) {
        printf("bind failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    //----------------------
    // Listen for incoming connection requests.
    // on the created socket
    if (listen(ListenSocket, 1) == SOCKET_ERROR) {
        printf("listen failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    //----------------------
    // Create a SOCKET for accepting incoming requests.
    SOCKET AcceptSocket;
    printf("Waiting for client to connect...\n");

    //----------------------
    // Accept the connection.
    AcceptSocket = accept(ListenSocket, NULL, NULL);
    if (AcceptSocket == INVALID_SOCKET) {
        printf("accept failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    else
        pOutput ();   // we have a connection request so do the callback

    // No longer need server socket
    closesocket(ListenSocket);

    WSACleanup();
    return 0;
}

// our callback which is invoked whenever a connection is made.
void printOut(void)
{
    printf("connection received.\n");
}

#include <process.h>

int main()
{
     // start up our listen server and provide a callback
    _beginthreadex(NULL, 0, ioThread, printOut, 0, NULL);
    // do other things while waiting for a connection. In this case
    // just sleep for a while.
    Sleep(30000);
}

  • Hervorragende Antwort, die sowohl synchrone als auch asynchrone Rückrufe anzeigt. Ein weiteres konkretes Beispiel für die Verwendung von asynchronen Rückrufen in C-*NIX sind asynchrone Signale und ihre Signalhandler. Hier ist eine hervorragende Beschreibung, wie Signal-Handler in Linux verarbeitet werden https://stackoverflow.com/questions/142789/what-is-a-callback-in-c-and-how-are-they-implemented (stackoverflow.com/questions/6949025/…).

    – drollig

    19. Mai 2020 um 10:21 Uhr

Benutzeravatar von John Millikin
John Millikin

Callbacks in C werden normalerweise mit Funktionszeigern und einem zugehörigen Datenzeiger implementiert. Sie bestehen Ihre Funktion on_event() und Datenzeiger auf eine Rahmenfunktion watch_events() (zum Beispiel). Wenn ein Ereignis eintritt, wird Ihre Funktion mit Ihren Daten und einigen ereignisspezifischen Daten aufgerufen.

Callbacks werden auch in der GUI-Programmierung verwendet. Das GTK+-Tutorial hat einen schönen Abschnitt über die Theorie der Signale und Rückrufe.

  • Hervorragende Antwort, die sowohl synchrone als auch asynchrone Rückrufe anzeigt. Ein weiteres konkretes Beispiel für die Verwendung von asynchronen Rückrufen in C-*NIX sind asynchrone Signale und ihre Signalhandler. Hier ist eine hervorragende Beschreibung, wie Signal-Handler in Linux verarbeitet werden https://stackoverflow.com/questions/142789/what-is-a-callback-in-c-and-how-are-they-implemented (stackoverflow.com/questions/6949025/…).

    – drollig

    19. Mai 2020 um 10:21 Uhr

Leonards Benutzeravatar
Leonard

Dies Wikipedia-Artikel hat ein Beispiel in C.

Ein gutes Beispiel ist, dass neue Module, die geschrieben wurden, um den Apache-Webserver zu erweitern, sich beim Apache-Hauptprozess registrieren, indem sie ihnen Funktionszeiger übergeben, damit diese Funktionen zurückgerufen werden, um Webseitenanforderungen zu verarbeiten.

1425150cookie-checkWas ist ein “Callback” in C und wie werden sie implementiert?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy