Что такое «обратный вызов» в C и как они реализованы?

153

Из того, что я прочитал, Core Audio в значительной степени опирается на обратные вызовы (и C ++, но это уже другая история).

Я понимаю концепцию (своего рода) настройки функции, которая неоднократно вызывается другой функцией для выполнения задачи. Я просто не понимаю, как они настроены и как они на самом деле работают. Любые примеры будут оценены.

noizetoys
источник

Ответы:

203

В C нет «обратного вызова» - не более, чем любая другая общая концепция программирования.

Они реализованы с использованием указателей на функции. Вот пример:

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, который возвращает значение random-ish, и передали указатель на него populate_array. populate_arrayвызовем нашу функцию обратного вызова 10 раз и назначим возвращенные значения элементам в данном массиве.

AIB
источник
2
Я могу ошибаться, но не должна быть строка в populate_array, которая вызывает указатель функции: array [i] = (* getNextValue) (); ?
Натан Феллман
40
Оператор разыменования является необязательным для указателей функций, как и оператор адреса. MyFunc (...) = (* MyFunc) (...) и & MyFunc = MyFunc
AIB
1
@NathanFellman Я только что прочитал Программирование на Expert C, и оно хорошо объясняет вызов функции указателя.
Мэтт Кларксон
1
@johnny Потому что так сказано в стандарте. Посмотрите на оставленный комментарий.
AIB
3
@Patrick: populateArray находится в библиотеке (и был написан 12 лет назад), а вы сами написали getNextRandomValue (вчера); поэтому он не может вызвать это напрямую. Подумайте о функции сортировки библиотеки, для которой вы сами предоставляете компаратор.
AIB
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);
Рассел Брайант
источник
Как раз то, что мне было нужно. Часть userdata очень полезна, если ваши пользователи хотят передать пользовательские данные (например, дескрипторы устройства), необходимые для функции обратного вызова.
Uceumern
вопрос проверки: является ли callback typedef звездочкой, потому что это указатель на адрес функции? Если звездочка отсутствует, это будет неправильно? Если это неверно , то есть две недостающие звезды в libsrtp библиотеке сиг на GitHub: github.com/cisco/libsrtp/blob/... github.com/cisco/libsrtp/blob/...
twildeman
@twildeman Кажется тривиальным ответить на свой вопрос, скомпилировав его в режиме Standard 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 является эквивалентом параметра / переменной функции, назначенной для использования в другой функции. Пример вики

В приведенном ниже коде

#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, вы можете назначить другую функцию для запуска внутри цикла с момента ее создания.

daemondave
источник
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

Обычно это можно сделать с помощью указателя на функцию, то есть специальной переменной, которая указывает на место в памяти функции. Затем вы можете использовать это для вызова функции с конкретными аргументами. Так что, вероятно, будет функция, которая устанавливает функцию обратного вызова. Это примет указатель функции и затем сохранит этот адрес где-нибудь, где он может быть использован. После этого, когда указанное событие инициируется, оно будет вызывать эту функцию.

MDEC
источник
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);
}
Как будто
источник