Was ist ein "Rückruf" in C und wie werden sie implementiert?

153

Nach der Lektüre, die ich gemacht habe, stützt sich Core Audio stark auf Rückrufe (und C ++, aber das ist eine andere Geschichte).

Ich verstehe das Konzept (eine Art), eine Funktion einzurichten, die von einer anderen Funktion wiederholt aufgerufen wird, um eine Aufgabe zu erfüllen. Ich verstehe einfach nicht, wie sie eingerichtet werden und wie sie tatsächlich funktionieren. Alle Beispiele wäre dankbar.

Noizetoys
quelle

Antworten:

203

In C gibt es keinen "Rückruf" - nicht mehr als bei jedem anderen generischen 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 nimmt die populate_arrayFunktion einen Funktionszeiger als dritten Parameter und ruft ihn auf, um die Werte abzurufen, mit denen das Array gefüllt werden soll. Wir haben den Rückruf geschrieben getNextRandomValue, der einen zufälligen Wert zurückgibt, und einen Zeiger darauf übergeben populate_array. populate_arrayruft unsere Rückruffunktion 10 Mal auf und weist die zurückgegebenen Werte den Elementen im angegebenen Array zu.

aib
quelle
2
Ich kann mich hier irren, sollte aber nicht die Zeile in populate_array sein, die den Funktionszeiger aufruft: array [i] = (* getNextValue) (); ?
Nathan Fellman
40
Der Dereferenzierungsoperator ist optional mit Funktionszeigern, ebenso wie die Adresse des Operators. myfunc (...) = (* myfunc) (...) und & myfunc = myfunc
aib
1
@ NathanFellman Ich habe gerade Expert C Programming gelesen und es erklärt den Funktionszeiger, der gut aufruft.
Matt Clarkson
1
@ Johnny Weil der Standard es sagt. Schauen Sie sich den positiv bewerteten Kommentar an.
Aib
3
@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. Stellen Sie sich eine Bibliothekssortierfunktion vor, an die Sie den Komparator selbst liefern.
Aib
121

Hier ist ein Beispiel für Rückrufe in C.

Angenommen, Sie möchten einen Code schreiben, mit dem die Registrierung von Rückrufen aufgerufen werden kann, wenn ein Ereignis auftritt.

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

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

Definieren Sie nun eine Funktion, mit der ein Rückruf registriert wird:

int event_cb_register(event_cb_t cb, void *userdata);

So würde Code aussehen, der einen Rückruf 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 Ereignis-Dispatchers kann der Rückruf in einer Struktur gespeichert werden, die ungefähr so ​​aussieht:

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

So sieht der Code aus, der einen Rückruf ausführt.

struct event_cb *callback;

...

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

callback->cb(event, callback->data);
Russell Bryant
quelle
Genau das, was ich brauchte. Der Benutzerdatenteil ist sehr hilfreich, wenn Ihre Benutzer benutzerdefinierte Daten (z. B. Gerätehandles) übergeben möchten, die für die Rückruffunktion erforderlich sind.
Uceumern
Überprüfungsfrage: Ist der Rückruf typedef mit einem Sternchen versehen, da er ein Zeiger auf die Funktionsadresse ist? Wenn das Sternchen fehlt, wäre das falsch? Wenn das nicht stimmt, fehlen zwei Sterne in der libsrtp-Bibliothek von Cisco auf github: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…
twildeman
@twildeman Es scheint trivial, 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 der in libsrtpgibt keine Warnungen. Ich gehe also davon aus, dass, wenn ein solcher Typ als Funktionsargument erscheint, er in einen Zeiger auf eine Funktion "zerfallen" muss, genau wie Arrays in Zeiger auf ihre ersten Elemente zerfallen, so dass am Ende dasselbe passiert in jedem Fall. Es ist jedoch interessant, dass Diskussionen über solche Typedefs, die ich gefunden habe, nicht einmal einen Blick auf diesen Aspekt werfen, sondern sich darauf konzentrieren, Prototypen oder Zeiger damit zu deklarieren
underscore_d
Ich habe keine Ahnung, was dies 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
20

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;
}
Gautham Kantharaju
quelle
9

Eine Rückruffunktion in C entspricht einem Funktionsparameter / einer Funktionsvariablen, 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 "Zurückrufen" / Ausführen aus PrintTwoNumbers heraus, wie vom Code während der Ausführung vorgegeben.

Wenn Sie also so etwas wie eine pthread-Funktion hatten, können Sie eine andere Funktion zuweisen, die ab der Instanziierung innerhalb der Schleife ausgeführt wird.

daemondave
quelle
6

Ein Rückruf in C ist eine Funktion, die einer anderen Funktion zur Verfügung gestellt wird, um sie irgendwann zurückzurufen, wenn die andere Funktion ihre Aufgabe erfüllt.

Es gibt zwei Möglichkeiten, wie ein Rückruf verwendet wird : synchroner Rückruf und asynchroner Rückruf. Ein synchroner Rückruf wird an eine andere Funktion gesendet, die eine Aufgabe ausführt und dann mit abgeschlossener Aufgabe zum Anrufer zurückkehrt. Ein asynchroner Rückruf wird an eine andere Funktion gesendet, 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 an 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. Diese beiden Funktionen nehmen einen Rückruf entgegen, der während der von der Funktion bereitgestellten Aufgabe verwendet wird, so dass der Typ der Daten im Fall von durchsucht oder im Fall von bsearch()sortiert wirdqsort() der Funktion nicht bekannt sein muss gebraucht.

Zum Beispiel ist hier ein kleines Beispielprogramm mit bsearch()verschiedenen Vergleichsfunktionen, synchronen Rückrufen. Indem wir den Datenvergleich an eine Rückruffunktion delegieren können, bsearch()können wir zur Laufzeit entscheiden, welche Art von Vergleich wir verwenden möchten. Dies ist synchron, da bsearch()die Aufgabe abgeschlossen ist, wenn die Funktion zurückgegeben wird.

#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 bei asynchronen E / A verwendet, bei denen eine E / A-Operation gestartet wird. Wenn sie abgeschlossen ist, wird der Rückruf aufgerufen.

Im folgenden Programm erstellen wir einen Socket zum Abhören von TCP-Verbindungsanforderungen. Wenn eine Anforderung empfangen wird, ruft die Funktion, die das Abhören ausführt, die bereitgestellte Rückruffunktion auf. Diese einfache Anwendung kann ausgeführt werden, indem Sie sie in einem Fenster ausführen, während Sie mit dem telnetDienstprogramm oder einem Webbrowser versuchen, eine Verbindung in einem anderen Fenster herzustellen.

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

Diese Anwendung startet a listen()auf dem lokalen Host 127.0.0.1 über Port 8282, sodass Sie entweder telnet 127.0.0.1 8282oder verwenden können 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 werden, und pthreadsstattdessen würde die Windows-Thread-Bibliothek verwendet .

#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);
}
Richard Chambers
quelle
Hervorragende Antwort mit synchronen und asynchronen Rückrufen. Ein weiteres konkretes Beispiel für die Verwendung von asynchronen Rückrufen in C- * NIX sind asynchrone Signale und ihre Signalhandler. Hier finden Sie eine hervorragende Beschreibung der Verarbeitung von Signalhandlern unter Linux [link] ( stackoverflow.com/questions/6949025/… ).
Drlolly
4

Rückrufe in C werden normalerweise unter Verwendung von Funktionszeigern und einem zugehörigen Datenzeiger implementiert. Sie übergeben Ihre Funktions- on_event()und Datenzeiger beispielsweise an eine Framework-Funktion watch_events(). Wenn ein Ereignis eintritt, wird Ihre Funktion mit Ihren Daten und einigen ereignisspezifischen Daten aufgerufen.

Rückrufe werden auch in der GUI-Programmierung verwendet. Das GTK + Tutorial enthält einen schönen Abschnitt zur Theorie der Signale und Rückrufe .

John Millikin
quelle
2

Dieser Wikipedia-Artikel enthält ein Beispiel in C.

Ein gutes Beispiel ist, dass neue Module, die geschrieben wurden, um das Apache-Webserverregister mit dem Haupt-Apache-Prozess zu erweitern, indem sie Funktionszeiger übergeben, sodass diese Funktionen zurückgerufen werden, um Webseitenanforderungen zu verarbeiten.

Leonard
quelle
0

Normalerweise kann dies mithilfe eines Funktionszeigers erfolgen, dh einer speziellen Variablen, die auf den Speicherort einer Funktion verweist. Sie können dies dann verwenden, um die Funktion mit bestimmten Argumenten aufzurufen. Es wird also wahrscheinlich eine Funktion geben, die die Rückruffunktion festlegt. Dies akzeptiert einen Funktionszeiger und speichert diese Adresse dann irgendwo, wo sie verwendet werden kann. Wenn danach das angegebene Ereignis ausgelöst wird, wird diese Funktion aufgerufen.

mdec
quelle
0

Es ist viel einfacher, eine Idee anhand eines Beispiels zu verstehen. Was bisher über die Rückruffunktion in C gesagt wurde, sind großartige Antworten, aber der wahrscheinlich größte Vorteil der Verwendung dieser Funktion besteht darin, den Code sauber und übersichtlich zu halten.

Beispiel

Der folgende C-Code implementiert eine schnelle Sortierung. Die interessanteste Zeile im folgenden Code ist diese, in der wir die Rückruffunktion in Aktion sehen können:

qsort(arr,N,sizeof(int),compare_s2b);

Compare_s2b ist der Name der Funktion, mit der qsort () die Funktion aufruft. Dadurch bleibt qsort () übersichtlich (daher einfacher zu warten). Sie rufen eine Funktion nur innerhalb einer anderen Funktion beim Namen auf (natürlich muss zumindest die Funktionsprototypdeklaration vorangestellt werden, bevor sie von einer anderen Funktion aufgerufen werden kann).

Der vollständige Code

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

int arr[]={56,90,45,1234,12,3,7,18};
//function prototype declaration 

int compare_s2b(const void *a,const void *b);

int compare_b2s(const void *a,const void *b);

//arranges the array number from the smallest to the biggest
int compare_s2b(const void* a, const void* b)
{
    const int* p=(const int*)a;
    const int* q=(const int*)b;

    return *p-*q;
}

//arranges the array number from the biggest to the smallest
int compare_b2s(const void* a, const void* b)
{
    const int* p=(const int*)a;
    const int* q=(const int*)b;

    return *q-*p;
}

int main()
{
    printf("Before sorting\n\n");

    int N=sizeof(arr)/sizeof(int);

    for(int i=0;i<N;i++)
    {
        printf("%d\t",arr[i]);
    }

    printf("\n");

    qsort(arr,N,sizeof(int),compare_s2b);

    printf("\nSorted small to big\n\n");

    for(int j=0;j<N;j++)
    {
        printf("%d\t",arr[j]);
    }

    qsort(arr,N,sizeof(int),compare_b2s);

    printf("\nSorted big to small\n\n");

    for(int j=0;j<N;j++)
    {
        printf("%d\t",arr[j]);
    }

    exit(0);
}
Als ob
quelle