Що таке "зворотний дзвінок" в C і як вони реалізовані?


153

З читання, яке я провів, Core Audio багато в чому покладається на зворотні дзвінки (і на C ++, але це вже інша історія).

Я розумію концепцію (свого роду) встановлення функції, яка викликається іншою функцією неодноразово для виконання завдання. Я просто не розумію, як вони налаштовані і як вони насправді працюють. Будь-які приклади будуть вдячні.

Відповіді:


203

У З немає ніякого "зворотного дзвінка" - не більше ніж будь-яка інша концепція загального програмування.

Вони реалізовані за допомогою покажчиків функцій. Ось приклад:

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

Тут populate_arrayфункція приймає вказівник функції як третій параметр і викликає її для отримання значень для заповнення масиву. Ми написали зворотний виклик getNextRandomValue, який повертає випадкове значення ish і передав на нього покажчик populate_array. populate_arrayбуде викликати нашу функцію зворотного виклику 10 разів та призначити повернені значення елементам у заданому масиві.


2
Я можу помилятися тут, але чи не повинен бути рядок у populate_array, який викликає вказівник функції: array [i] = (* getNextValue) (); ?
Натан Фелман

40
Оператор дереференції є необов'язковим з функціональними покажчиками, як і оператор addressof. MyFunc (...) = (* MyFunc) (...) і & MyFunc = MyFunc
AIB

1
@NathanFellman Я щойно прочитав програмування Expert C, і це добре пояснює функцію вказівника виклику.
Метт Кларксон

1
@johnny Тому що стандарт говорить так. Подивіться на схвалений коментар.
AIB

3
@Patrick: populateArray знаходиться в бібліотеці (написана 12 років тому), і ви самі написали getNextRandomValue (вчора); тому він не може зателефонувати безпосередньо. Подумайте про функцію сортування бібліотеки, до якої ви самі постачаєте компаратор.
аїб

121

Ось приклад зворотних викликів у C.

Скажімо, ви хочете написати якийсь код, який дозволяє реєструвати зворотні дзвінки, коли викликається якась подія.

Спочатку визначте тип функції, яка використовується для зворотного дзвінка:

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

Тепер визначте функцію, яка використовується для реєстрації зворотного дзвінка:

int event_cb_register(event_cb_t cb, void *userdata);

Ось як виглядав би код, який реєструє зворотний виклик:

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

У внутрішніх даних диспетчера подій зворотний виклик може зберігатися у структурі, яка виглядає приблизно так:

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

Ось так виглядає код, який виконує зворотний виклик.

struct event_cb *callback;

...

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

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

Тільки те, що мені було потрібно. Частина даних користувача дуже корисна, якщо ваші користувачі хочуть передати користувацькі дані (наприклад, ручки пристроїв), необхідні у функції зворотного виклику.
uceumern

питання підтвердження: Чи є тип зворотного дзвінкаdef зірочкою, оскільки він є вказівником на адресу функції? Якщо зірочка відсутня, то це буде неправильно? Якщо це невірно, у бібліотеці libsrtp cisco на github є дві зірки: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…
twildeman

@twildeman Начебто тривіально відповісти на власне запитання, склавши в стандартному режимі C із попередженнями. Ви також можете написати мінімізовану програму тестування. Такий код, як у libsrtpвказаному, не попереджає. Тож я припускаю, що коли такий тип з'являється як аргумент функції, потрібно «розпастися» на покажчик на функцію, подібно до того, як масиви розпадаються на покажчики на їх перші елементи, так що в кінці кінців відбувається те ж саме в будь-якому випадку. Це є цікавим, однак, що обговорення таких визначень типів , які я знайшов НЕ навіть погляд на цьому аспекті, а зосередитися на оголошення прототипів або покажчики з ним
underscore_d

Я поняття не маю, що це робить, і це неможливо успішно скласти. Чи може хтось пояснити це детально або заповнити решту коду до того, щоб компілювати успішно?
Енді Лін

20

Проста програма зворотного дзвінка. Сподіваюся, що він відповідає на ваше запитання.

#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

Функція зворотного виклику в C - еквівалент параметру / змінної функції, призначеного для використання в іншій функції. Приклад Wiki

У наведеному нижче коді:

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

Функція (* numberSource) всередині виклику функції PrintTwoNumbers - це функція "зворотного дзвінка" / виконання зсередини PrintTwoNumbers, як це продиктовано кодом під час її запуску.

Отже, якщо у вас було щось на зразок функції pthread, ви можете призначити іншу функцію, щоб запустити всередині циклу з його інстанції.


6

Зворотний виклик в C - це функція, яка надається іншій функції, щоб "передзвонити до" в якийсь момент, коли інша функція виконує своє завдання.

Існує два способи використання зворотного дзвінка : синхронний зворотні виклик і асинхронний зворотний виклик. Синхронний зворотний виклик надається іншій функції, яка збирається виконати якусь задачу, а потім повертається до абонента з виконанням завдання. Асинхронний зворотний виклик надається іншій функції, яка збирається запустити завдання, а потім повернутися до абонента, якщо завдання, можливо, не виконане.

Зазвичай синхронний зворотний виклик використовується для надання делегата іншої функції, якій інша функція делегує деякий крок завдання. Класичними прикладами цієї делегації є функції bsearch()та qsort()з Бібліотеки стандартних стандартів С. Обидві ці функції приймають зворотний виклик, який використовується під час завдання, яке функція забезпечує таким чином, що тип даних, що шукаються, у випадку bsearch()або сортованих у випадку qsort(), не потрібно знати, коли функція використовується б / в.

Наприклад, ось невелика прикладна програма з bsearch()використанням різних функцій порівняння, синхронних зворотних зворотів. Дозволяючи нам делегувати порівняння даних функції зворотного виклику,bsearch() функція дозволяє нам вирішувати під час виконання, яке порівняння ми хочемо використовувати. Це синхронно, тому що коли bsearch()функція повертається, завдання завершено.

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

Асинхронний зворотний виклик відрізняється тим, що коли повертається викликана функція, до якої ми надаємо зворотний виклик, завдання може не бути виконаним. Цей тип зворотного виклику часто використовується з асинхронним входом / виводом, в якому запускається операція вводу / виводу, а потім після її завершення викликається зворотний виклик.

У наступній програмі ми створюємо сокет для прослуховування запитів на підключення TCP, і коли запит отримано, функція, що прослуховує, викликає надану функцію зворотного виклику. Цей простий додаток можна використовувати, запустивши його в одному вікні, використовуючиtelnet утиліту або веб-браузер, щоб спробувати підключитися в іншому вікні.

Я зняв більшу частину коду WinSock із прикладу, який Microsoft забезпечує accept()функцією за адресою https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx

Ця програма запускає listen()локальний хост, 127.0.0.1, використовуючи порт 8282, щоб ви могли використовувати або telnet 127.0.0.1 8282абоhttp://127.0.0.1:8282/ .

Цей зразок програми створено як консольний додаток із Visual Studio 2017 Community Edition, і він використовує версію розеток Microsoft WinSock. Для додатка Linux функції WinSock потрібно буде замінити на альтернативи Linux, а pthreadsзамість цього використовуватиметься бібліотека потоків Windows .

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

Відмінна відповідь: показ синхронних та асинхронних зворотних дзвінків. Іншим конкретним прикладом використання асинхронних зворотних дзвінків у C- * NIX є асинхронні сигнали та їх обробники сигналів. Ось чудовий опис того, як обробляються сигнали в Linux [посилання] ( stackoverflow.com/questions/6949025/… ).
drlolly

4

Зворотні виклики в C зазвичай реалізуються за допомогою функціональних покажчиків та пов'язаного з ними вказівника даних. Ви передаєте свою функцію on_event()та покажчики даних у рамкову функцію watch_events()(наприклад). Коли подія трапляється, ваша функція викликається вашими даними та деякими конкретними даними події.

Відклики викликів також використовуються в програмуванні GUI. У підручнику GTK + є приємний розділ з теорії сигналів та зворотних викликів .


2

Ця стаття у Вікіпедії є прикладом у C.

Хорошим прикладом є те, що нові модулі, написані для розширення реєстру веб-сервера Apache основним процесом apache, передаючи їм покажчики функцій, тому ці функції викликаються назад для обробки запитів веб-сторінок.


0

Зазвичай це можна зробити за допомогою покажчика функції, тобто спеціальної змінної, яка вказує на місце в пам'яті функції. Потім ви можете використовувати цю функцію для виклику функції за допомогою конкретних аргументів. Тому, ймовірно, буде функція, яка встановлює функцію зворотного дзвінка. Це прийме покажчик функції, а потім збереже цю адресу десь там, де вона може бути використана. Після цього, коли вказана подія спрацьовує, вона викличе цю функцію.


0

Набагато простіше зрозуміти ідею на прикладі. Те, що було сказано про функцію зворотного дзвінка в C, - це чудові відповіді, але, мабуть, найбільша перевага використання цієї функції полягає в тому, щоб зберегти код чистим і незабрудненим.

Приклад

Наступний код C реалізує швидке сортування. Найцікавіший рядок у наведеному нижче коді - це цей, де ми можемо бачити функцію зворотного виклику в дії:

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

Compare_s2b - це назва функції, яку qsort () використовує для виклику функції. Це зберігає qsort () настільки незабрудненим (отже, простішим у обслуговуванні). Ви просто викликаєте функцію по імені зсередини іншої функції (звичайно, декларація прототипу функції, принаймні, повинна попередньо передувати, перш ніж її можна буде викликати з іншої функції).

Повний кодекс

#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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.