Qu’est-ce qu’un «rappel» en C et comment sont-ils mis en œuvre?

A la lecture de ce que j’ai fait, Core Audio s’appuie fortement sur les callbacks (et C ++, mais c’est une autre histoire).

Je comprends le concept (en quelque sorte) de mettre en place une fonction appelée à plusieurs resockets par une autre fonction pour accomplir une tâche. Je ne comprends tout simplement pas comment ils se mettent en place et comment ils fonctionnent réellement. Des exemples seraient appréciés.

Il n’y a pas de “rappel” en C – pas plus que tout autre concept de programmation générique.

Ils sont implémentés à l’aide de pointeurs de fonctions. Voici un exemple:

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

Ici, la fonction populate_array prend un pointeur de fonction comme troisième paramètre et l'appelle pour obtenir les valeurs à remplir avec le tableau. Nous avons écrit le rappel getNextRandomValue , qui renvoie une valeur aléatoire-ish, et lui a transmis un pointeur vers populate_array . populate_array appellera notre fonction de rappel 10 fois et assignera les valeurs renvoyées aux éléments du tableau donné.

Voici un exemple de callbacks en C.

Supposons que vous souhaitiez écrire du code permettant d’appeler les rappels lorsqu’un événement se produit.

Définissez d’abord le type de fonction utilisé pour le rappel:

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

Maintenant, définissez une fonction utilisée pour enregistrer un rappel:

 int event_cb_register(event_cb_t cb, void *userdata); 

Voici à quoi ressemblerait le code qui enregistre un rappel:

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

Dans les composants internes du répartiteur d’événements, le rappel peut être stocké dans une structure qui ressemble à ceci:

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

Voici à quoi ressemble le code qui exécute un rappel.

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

Un simple programme de rappel. J’espère que cela répond à votre question.

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

Une fonction de rappel dans C est l’équivalent d’un paramètre / d’une variable de fonction à utiliser dans une autre fonction. Exemple de wiki

Dans le code ci-dessous,

 #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 fonction (* numberSource) à l’intérieur de l’appel de la fonction PrintTwoNumbers est une fonction permettant de “rappeler” / exécuter depuis PrintTwoNumbers en fonction du code utilisé.

Donc, si vous aviez quelque chose comme une fonction pthread, vous pourriez assigner une autre fonction à exécuter à l’intérieur de la boucle à partir de son instanciation.

Les rappels en C sont généralement implémentés à l’aide de pointeurs de fonctions et d’un pointeur de données associé. Vous passez votre fonction on_event() et les pointeurs de données à une fonction de cadre watch_events() (par exemple). Lorsqu’un événement se produit, votre fonction est appelée avec vos données et certaines données spécifiques à un événement.

Les rappels sont également utilisés dans la programmation par interface graphique. Le tutoriel GTK + contient une section intéressante sur la théorie des signaux et des rappels .

Cet article de wikipedia a un exemple en C.

Un bon exemple est que les nouveaux modules écrits pour augmenter le serveur Web Apache s’enregistrent avec le processus apache principal en leur transmettant des pointeurs de fonction afin que ces fonctions soient rappelées pour traiter les demandes de pages Web.

Généralement, cela peut être fait en utilisant un pointeur de fonction, c’est-à-dire une variable spéciale qui pointe vers l’emplacement mémoire d’une fonction. Vous pouvez ensuite l’utiliser pour appeler la fonction avec des arguments spécifiques. Donc, il y aura probablement une fonction qui définit la fonction de rappel. Cela acceptera un pointeur de fonction et stockera ensuite cette adresse quelque part où elle peut être utilisée. Après cela, lorsque l’événement spécifié est déclenché, il appelle cette fonction.

Un rappel en C est une fonction fournie à une autre fonction pour “rappeler” à un moment donné lorsque l’autre fonction effectue sa tâche.

Un rappel est utilisé de deux manières : rappel synchrone et rappel asynchrone. Un rappel synchrone est fourni à une autre fonction qui va effectuer certaines tâches, puis retourner à l’appelant avec la tâche terminée. Un rappel asynchrone est fourni à une autre fonction qui va démarrer une tâche, puis retourner à l’appelant avec la tâche éventuellement non terminée.

Un rappel synchrone est généralement utilisé pour fournir un délégué à une autre fonction à laquelle l’autre fonction délègue une étape de la tâche. Les exemples classiques de cette délégation sont les fonctions bsearch() et qsort() de la bibliothèque C standard. Ces deux fonctions prennent un rappel qui est utilisé lors de la tâche fournie par la fonction, de sorte que le type des données recherchées, dans le cas de bsearch() , ou sortingées, dans le cas de `qsort (), n’a pas besoin être connu par la fonction utilisée.

Par exemple, voici un petit programme exemple avec bsearch() utilisant différentes fonctions de comparaison, des rappels synchrones. En nous permettant de déléguer la comparaison de données à une fonction de rappel, la fonction bsearch() nous permet de décider au moment de l’exécution quel type de comparaison nous voulons utiliser. Ceci est synchrone car lorsque la fonction bsearch() renvoie la tâche est terminée.

 #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 rappel asynchrone est différent en ce sens que lorsque la fonction appelée à laquelle nous fournissons un rappel est renvoyée, la tâche peut ne pas être terminée. Ce type de rappel est souvent utilisé avec les E / S asynchrones dans lesquelles une opération d'E / S est lancée, puis, une fois terminée, le rappel est appelé.

Dans le programme suivant, nous créons un socket pour écouter les demandes de connexion TCP et lorsqu'une requête est reçue, la fonction qui écoute écoute la fonction de rappel fournie. Cette application simple peut être exercée en l'exécutant dans une fenêtre tout en utilisant l'utilitaire telnet ou un navigateur Web pour tenter de se connecter dans une autre fenêtre.

J'ai soulevé la plupart du code WinSock à partir de l'exemple fourni par Microsoft avec la fonction accept() à l' adresse https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx.

Cette application lance un listen() sur l’hôte local, 127.0.0.1, en utilisant le port 8282 pour pouvoir utiliser telnet 127.0.0.1 8282 ou http://127.0.0.1:8282/ .

Cet exemple d'application a été créé en tant qu'application de console avec Visual Studio 2017 Community Edition et utilise la version Microsoft WinSock des sockets. Pour une application Linux, les fonctions WinSock devraient être remplacées par les alternatives Linux et la bibliothèque de threads Windows utiliserait plutôt 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); }