Che cos'è un "callback" in C e come vengono implementati?


153

Dalla lettura che ho fatto, Core Audio si basa fortemente sui callback (e sul C ++, ma questa è un'altra storia).

Comprendo il concetto (una sorta di) di impostazione di una funzione che viene chiamata ripetutamente da un'altra funzione per eseguire un compito. Non capisco come si configurano e come funzionano davvero. Qualsiasi esempio sarebbe apprezzato.

Risposte:


203

Non c'è "callback" in C - non più di qualsiasi altro concetto di programmazione generico.

Sono implementati utilizzando i puntatori a funzione. Ecco un esempio:

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);
    ...
}

Qui, la populate_arrayfunzione accetta un puntatore a funzione come terzo parametro e lo chiama per ottenere i valori con cui popolare l'array. Abbiamo scritto il callback getNextRandomValue, che restituisce un valore ish casuale e gli abbiamo passato un puntatore populate_array. populate_arraychiamerà la nostra funzione di callback 10 volte e assegnerà i valori restituiti agli elementi nell'array specificato.


2
Potrei sbagliarmi qui, ma la riga in populate_array che chiama il puntatore della funzione non dovrebbe essere: array [i] = (* getNextValue) (); ?
Nathan Fellman,

40
L'operatore dereference è facoltativo con i puntatori a funzione, così come l'indirizzo dell'operatore. myfunc (...) = (* myfunc) (...) e & myfunc = myfunc
aib

1
@NathanFellman Ho appena letto la Programmazione Expert C e spiega il puntatore alla funzione che chiama bene.
Matt Clarkson,

1
@johnny Perché lo standard lo dice. Guarda il commento votato.
aib

3
@Patrick: populateArray è in una libreria (ed è stato scritto 12 anni fa) e tu stesso hai scritto getNextRandomValue (ieri); quindi non può chiamarlo direttamente. Pensa a una funzione di ordinamento della libreria a cui fornisci tu stesso il comparatore.
aib

121

Ecco un esempio di callback in C.

Supponiamo che tu voglia scrivere un codice che consenta di richiamare la registrazione di callback quando si verifica un evento.

Definire innanzitutto il tipo di funzione utilizzata per il callback:

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

Ora, definire una funzione che viene utilizzata per registrare un callback:

int event_cb_register(event_cb_t cb, void *userdata);

Ecco come apparirebbe il codice che registra un callback:

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);
...

Negli interni del dispatcher di eventi, il callback può essere archiviato in una struttura simile a questa:

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

Ecco come appare il codice che esegue un callback.

struct event_cb *callback;

...

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

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

Proprio quello di cui avevo bisogno. La parte userdata è molto utile se i tuoi utenti vogliono trasmettere dati personalizzati (ad es. Handle di dispositivo) richiesti nella funzione di callback.
martedì

domanda di verifica: il callback è tipizzato con un asterisco perché è un puntatore all'indirizzo della funzione? Se manca l'asterisco, sarebbe errato? Se ciò non è corretto, allora ci sono due stelle mancanti nella libreria libsrtp di cisco su github: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…
twildeman

@twildeman Sembra banale rispondere alla tua domanda compilando in modalità C standard con avvertenze attive. Puoi anche scrivere un programma di test minimizzato. Codice come quelli in libsrtpnon fornisce avvisi. Presumo, quindi, che quando un tale tipo appare come argomento di funzione, è necessario 'decadere' in un puntatore a funzione, proprio come gli array decadono in puntatori ai loro primi elementi, quindi alla fine accade la stessa cosa in entrambi i casi. Si è interessante, però, che le discussioni di tali typedef che ho trovato non hanno nemmeno sguardo a questo aspetto, piuttosto concentrarsi sulla proclamazione prototipi o puntatori con esso
underscore_d

Non ho idea di cosa faccia, e non è possibile compilarlo con successo. Qualcuno può spiegarlo in modo dettagliato o riempire il resto del codice per compilarlo correttamente?
Andy Lin,

20

Un semplice programma di richiamata. Spero che risponda alla tua domanda.

#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;
}

9

Una funzione di callback in C è l'equivalente di un parametro / variabile di funzione assegnato da utilizzare all'interno di un'altra funzione. Esempio Wiki

Nel codice seguente,

#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;
}

La funzione (* numberSource) all'interno della chiamata di funzione PrintTwoNumbers è una funzione per "richiamare" / eseguire dall'interno di PrintTwoNumbers come dettato dal codice durante l'esecuzione.

Quindi, se avessi qualcosa come una funzione pthread, potresti assegnare un'altra funzione da eseguire all'interno del loop dalla sua istanza.


6

Un callback in C è una funzione che viene fornita a un'altra funzione per "richiamare" ad un certo punto quando l'altra funzione sta facendo il suo compito.

Esistono due modi in cui viene utilizzato un callback : callback sincrono e callback asincrono. Un callback sincrono viene fornito a un'altra funzione che eseguirà alcune attività e quindi tornerà al chiamante con l'attività completata. Un callback asincrono viene fornito a un'altra funzione che avvia un'attività e quindi ritorna al chiamante con l'attività che probabilmente non è stata completata.

Un callback sincrono viene in genere utilizzato per fornire un delegato a un'altra funzione a cui l'altra funzione delega un passaggio dell'attività. Esempi classici di questa delegazione sono le funzioni bsearch()e qsort()dalla libreria standard C. Entrambe queste funzioni accettano un callback che viene utilizzato durante l'attività fornita dalla funzione in modo tale che il tipo di dati ricercati, nel caso bsearch()o ordinati, nel caso di qsort(), non debba essere conosciuto dalla funzione Usato.

Ad esempio, ecco un piccolo programma di esempio che bsearch()utilizza diverse funzioni di confronto, richiamate sincrone. Consentendoci di delegare il confronto dei dati a una funzione di callback, la bsearch()funzione ci consente di decidere in fase di esecuzione quale tipo di confronto vogliamo usare. Questo è sincrono perché quando la bsearch()funzione restituisce l'attività è completa.

#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);
    }
}

Un callback asincrono è diverso dal fatto che quando la funzione chiamata a cui forniamo un callback ritorna, l'attività potrebbe non essere completata. Questo tipo di callback viene spesso utilizzato con l'I / O asincrono in cui viene avviata un'operazione di I / O e quindi al termine viene richiamato il callback.

Nel seguente programma creiamo un socket per ascoltare le richieste di connessione TCP e quando viene ricevuta una richiesta, la funzione che fa l'ascolto invoca quindi la funzione di callback fornita. Questa semplice applicazione può essere esercitata eseguendola in una finestra mentre si utilizza l' telnetutilità o un browser Web per tentare di connettersi in un'altra finestra.

Ho estratto la maggior parte del codice WinSock dall'esempio fornito da Microsoft con la accept()funzione su https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx

Questa applicazione avvia un listen()host locale, 127.0.0.1, usando la porta 8282 in modo da poter usare telnet 127.0.0.1 8282o http://127.0.0.1:8282/.

Questa applicazione di esempio è stata creata come un'applicazione console con Visual Studio 2017 Community Edition e utilizza la versione di socket Microsoft WinSock. Per un'applicazione Linux le funzioni di WinSock dovrebbero essere sostituite con le alternative di Linux e la libreria di thread di Windows dovrebbe pthreadsinvece utilizzare .

#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);
}

Risposta eccellente, mostrando sia callback sincroni che asincroni. Un altro esempio concreto dell'uso di richiamate asincrone in C- * NIX sono i segnali asincroni e i loro gestori di segnali. Ecco una descrizione eccellente di come i gestori di segnali vengono elaborati in Linux [link] ( stackoverflow.com/questions/6949025/… ).
drlolly

4

I callback in C vengono generalmente implementati utilizzando i puntatori a funzione e un puntatore dati associato. I on_event()puntatori di dati e funzioni vengono passati a una funzione di framework watch_events()(ad esempio). Quando si verifica un evento, la tua funzione viene chiamata con i tuoi dati e alcuni dati specifici dell'evento.

I callback sono utilizzati anche nella programmazione della GUI. Il tutorial GTK + ha una bella sezione sulla teoria dei segnali e dei callback .


2

Questo articolo di Wikipedia ha un esempio in C.

Un buon esempio è che i nuovi moduli scritti per aumentare il registro del server Web Apache con il processo apache principale passandogli i puntatori di funzione in modo che tali funzioni vengano richiamate per elaborare le richieste della pagina Web.


0

Di solito questo può essere fatto utilizzando un puntatore a funzione, ovvero una variabile speciale che punta alla posizione di memoria di una funzione. È quindi possibile utilizzare questo per chiamare la funzione con argomenti specifici. Quindi probabilmente ci sarà una funzione che imposta la funzione di callback. Questo accetterà un puntatore a funzione e quindi memorizzerà quell'indirizzo da qualche parte dove può essere utilizzato. Successivamente, quando viene attivato l'evento specificato, chiamerà quella funzione.


0

È molto più facile capire un'idea attraverso l'esempio. Ciò che è stato detto finora sulla funzione di callback in C sono ottime risposte, ma probabilmente il più grande vantaggio dell'uso della funzione è di mantenere il codice pulito e ordinato.

Esempio

Il seguente codice C implementa l'ordinamento rapido. La riga più interessante nel codice qui sotto è questa, dove possiamo vedere la funzione di callback in azione:

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

Compare_s2b è il nome della funzione che qsort () sta usando per chiamare la funzione. Ciò mantiene qsort () così ordinato (quindi più facile da mantenere). Basta chiamare una funzione per nome dall'interno di un'altra funzione (ovviamente, la dichiarazione del prototipo di funzione, almeno, deve precedere prima di poter essere chiamata da un'altra funzione).

Il codice completo

#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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.