Che cos’è un “callback” in C e in che modo vengono implementati?

Dalla lettura che ho fatto, Core Audio fa molto affidamento sui callback (e sul C ++, ma questa è un’altra storia).

Comprendo il concetto (sorta di) di impostare una funzione che viene chiamata da un’altra funzione ripetutamente per eseguire un compito. Semplicemente non capisco come vengono impostati e come funzionano effettivamente. Qualsiasi esempio sarebbe apprezzato.

Non c’è “callback” in C – non più di qualsiasi altro concetto di programmazione generico.

Sono implementati usando i puntatori di funzione. Ecco un esempio:

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)) { for (size_t i=0; i 

Qui, la funzione populate_array accetta un puntatore a funzione come suo 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 lo ha passato a populate_array . populate_array chiamerà la nostra funzione di callback 10 volte e assegnerà i valori restituiti agli elementi dell'array dato.

Ecco un esempio di callback in C.

Supponiamo che tu voglia scrivere un codice che permetta di registrare i callback da chiamare quando si verifica qualche evento.

Prima definire il tipo di funzione utilizzata per il callback:

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

Ora, definire una funzione utilizzata per registrare un callback:

 int event_cb_register(event_cb_t cb, void *userdata); 

Questo è come apparirebbe il codice che registra una richiamata:

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

Nell’interno del dispatcher dell’evento, il callback può essere memorizzato in una struttura che assomiglia a qualcosa di simile a questo:

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

Questo è l’aspetto del codice che esegue una richiamata.

 struct event_cb *callback; ... /* Get the event_cb that you want to execute */ callback->cb(event, callback->data); 

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

 #include  #include  #include  #include  #include  #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 \tb:%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; } 

Una funzione di callback in C è l’equivalente di un parametro / variabile di funzione assegnato per essere utilizzato all’interno di un’altra funzione. Esempio di Wiki

Nel codice qui sotto,

 #include  #include  /* 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 da PrintTwoNumbers come dettato dal codice durante l’esecuzione.

Quindi, se avevi qualcosa come una funzione pthread, puoi assegnare un’altra funzione da eseguire all’interno del ciclo dalla sua istanziazione.

Le callback in C vengono solitamente implementate utilizzando i puntatori di funzione e un puntatore dati associato. Si passa la funzione on_event() e i puntatori di dati a una funzione 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.

Le callback vengono anche utilizzate nella programmazione GUI. Il tutorial GTK + ha una bella sezione sulla teoria dei segnali e dei callback .

Questo articolo di Wikipedia ha un esempio in C.

Un buon esempio è che i nuovi moduli scritti per aumentare il server Web Apache si registrano con il processo principale di apache passando loro i puntatori di funzione in modo che tali funzioni vengano richiamate per elaborare le richieste di pagine web.

Solitamente ciò può essere fatto usando un puntatore a funzione, cioè una variabile speciale che punta alla posizione di memoria di una funzione. È quindi ansible utilizzare questo per chiamare la funzione con argomenti specifici. Quindi ci sarà probabilmente 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 usato. Dopodiché, quando viene triggersto l’evento specificato, chiamerà quella funzione.

Una callback in C è una funzione che viene fornita ad un’altra funzione per “richiamare” ad un certo punto quando l’altra funzione sta svolgendo il proprio compito.

Esistono due modi in cui viene utilizzata una richiamata : callback sincrono e callback asincrono. Un callback sincrono viene fornito a un’altra funzione che sta per eseguire alcune attività e quindi tornare al chiamante con l’attività completata. Un callback asincrono viene fornito a un’altra funzione che sta per avviare un’attività e quindi torna al chiamante con l’attività eventualmente non completata.

Un callback sincrono viene in genere utilizzato per fornire un delegato a un’altra funzione a cui l’altra funzione delega alcuni passaggi dell’attività. Esempi classici di questa delega sono le funzioni bsearch() e qsort() della libreria C standard. Entrambe queste funzioni accettano una richiamata che viene utilizzata durante l’attività bsearch() dalla funzione in modo che il tipo di dati ricercati, nel caso di bsearch() o ordinato, nel caso di `qsort (), non sia necessario essere conosciuto dalla funzione utilizzata.

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

 #include  #include  #include  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 in quanto quando la funzione chiamata a cui viene restituito un callback, l'attività potrebbe non essere completata. Questo tipo di callback viene spesso utilizzato con I / O asincroni in cui viene avviata un'operazione di I / O e, al termine, viene richiamata la richiamata.

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

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

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

Questa applicazione di esempio è stata creata come applicazione console con Visual Studio 2017 Community Edition e sta utilizzando la versione di socket Microsoft WinSock. Per un'applicazione Linux, le funzioni di WinSock dovrebbero essere sostituite con le alternative Linux e la libreria di thread di Windows pthreads invece pthreads .

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