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

StackOverflow https://stackoverflow.com/questions/142789

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

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

Обратный вызов в 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=против 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 threads использовала бы 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);
}

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

Намного легче понять идею на примере.То, что было сказано о функции обратного вызова в 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);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top