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

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

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

Ответ 1

В 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 раз и назначит возвращаемые значения элементам в данном массиве.

Ответ 2

Вот пример обратных вызовов в 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);

Ответ 3

Простая программа обратного вызова. Надеюсь, он ответит на ваш вопрос.

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

Ответ 4

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

Ответ 5

Обратные вызовы на C обычно реализуются с помощью указателей функций и связанного указателя данных. Вы передаете свою функцию on_event() и указатели данных на фрейм-функцию watch_events() (например). Когда событие происходит, ваша функция вызывается с вашими данными и некоторыми конкретными событиями.

Обратные вызовы также используются в программировании графического интерфейса. GTK + tutorial имеет приятный раздел в теории сигналов и обратных вызовов.

Ответ 6

Эта статья в википедии содержит пример на C.

Хорошим примером является то, что новые модули, написанные для расширения регистра веб-сервера Apache с помощью основного процесса apache, передают им указатели на функции, чтобы эти функции вызывались обратно для обработки запросов веб-страниц.

Ответ 7

Обратный вызов в C - это функция, которая предоставляется другой функции для "обратного вызова" в какой-то момент, когда другая функция выполняет свою задачу.

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

Синхронный обратный вызов обычно используется для предоставления делегата другой функции, которой другая функция делегирует некоторый шаг задачи. Классическими примерами этой делегации являются функции bsearch() и qsort() из стандартной библиотеки C. Обе эти функции выполняют обратный вызов, который используется во время задачи, которую предоставляет функция, чтобы тип поиска данных в случае 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, а библиотека потоков Windows будет использовать pthreads.

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

Ответ 8

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

Ответ 9

Проще понять идею на примере. То, что было сказано о функции обратного вызова в 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);
}