Указатели функций и обратные вызовы в C

Я начал просматривать обратные вызовы. Я нашел эту ссылку на SO: Что такое "callback" в C и как они реализованы? У этого есть хороший пример обратного вызова, который очень похож на то, что мы используем на работе. Однако я попытался заставить его работать, но у меня много ошибок.

#include <stdio.h>

/* Is the actual function pointer? */
typedef void (*event_cb_t)(const struct event *evt, void *user_data);

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

int event_cb_register(event_ct_t cb, void *user_data);

static void my_event_cb(const struct event *evt, void *data)
{
    /* do some stuff */
}

int main(void)
{
    event_cb_register(my_event_cb, &my_custom_data);

    struct event_cb *callback;

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

    return 0;
}

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

  • Что означает "регистрация обратного вызова" и "диспетчер событий"?

Ответ 1

Этот код компилируется и запускается под GCC с помощью -Wall.

#include <stdio.h>

struct event_cb;

typedef void (*event_cb_t)(const struct event_cb *evt, void *user_data);

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

static struct event_cb saved = { 0, 0 };

void event_cb_register(event_cb_t cb, void *user_data)
{
    saved.cb = cb;
    saved.data = user_data;
}

static void my_event_cb(const struct event_cb *evt, void *data)
{
    printf("in %s\n", __func__);
    printf("data1: %s\n", (const char *)data);
    printf("data2: %s\n", (const char *)evt->data);
}

int main(void)
{
    char my_custom_data[40] = "Hello!";
    event_cb_register(my_event_cb, my_custom_data);

    saved.cb(&saved, saved.data);

    return 0;
}

Вероятно, вам нужно проверить, получает ли функция обратного вызова всю структуру event_cb или нет - обычно вы просто передаете данные, потому что, как показано, в противном случае у вас есть два источника одной и той же информации (и запасная копия указатель на функцию, в которой вы находитесь). Существует много исправлений, которые можно сделать на этом, но он работает.


Вопрос в комментариях спрашивает: это хороший пример обратного вызова?

Вкратце, нет, но отчасти потому, что здесь недостаточно инфраструктуры.

В некотором смысле вы можете думать о функции сравнения, переданной функциям qsort() или bsearch() в качестве обратного вызова. Это указатель на функцию, которая передается в общую функцию, которая выполняет то, что общая функция не может сделать для себя.

Другим примером обратного вызова является функция обработчика сигналов. Вы сообщаете системе, чтобы вызвать вашу функцию, когда происходит событие - сигнал. Вы заранее создали механизмы, чтобы, когда системе нужно вызвать функцию, она знает, какую функцию вызывать.

Пример кода пытается обеспечить более сложный механизм - обратный вызов с контекстом. В С++ это, возможно, будет функтором.

Некоторые из кода, с которым я работаю, имеют очень суетливые требования к управлению памятью - при использовании в конкретном контексте. Поэтому для тестирования я использую malloc() et al, но в процессе производства я должен установить распределители памяти в специализированные распределители. Затем я предоставляю вызов функции в пакете, так что суетливый код может переопределять распределенные распределители памяти по умолчанию со своими суррогатными версиями - и при условии, что суррогаты работают нормально, код будет вести себя как раньше. Это форма обратного вызова - опять-таки форма, которая не нуждается в большом количестве (или что-либо) в способе пользовательских контекстных данных.

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

Ответ 2

Что подразумевается под "регистрацией обратного вызова" и "диспетчером событий"?

"Регистрация обратного вызова" - это действие, указывающее базовой системе, какую именно функцию вызывать, и (необязательно) с какими параметрами, а также, возможно, для какого конкретного класса событий этот обратный вызов должен вызываться.

"Диспетчер событий" получает события от O/S (или GUI и т.д.) И фактически вызывает обратные вызовы, просматривая список зарегистрированных обратных вызовов, чтобы увидеть, кто заинтересован в этом событии.

Ответ 3

Без вывода компилятора это сложно, но я вижу несколько проблем;

int event_cb_register(event_ct_t cb, void *user_data);

Должно быть

int event_cb_register(event_cb_t cb, void *user_data);

Переменная my_custom_data не существует, когда она используется здесь;

event_cb_register(my_event_cb, &my_custom_data);

Этот указатель никогда не инициализируется;

struct event_cb *callback;

И в;

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

Вы не можете передать имя типа ('событие') функции, вы должны передать экземпляр этого типа.

Ответ 4

int event_cb_register(event_ct_t cb, void *user_data);

Что это за тип event_ct_t? Вы имеете в виду event_cb_t?

struct event_cb *callback;

Создает неинициализированный указатель на структуру event_cb. Обратите внимание, что это указывает на мусор.

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

Вы пытаетесь вызвать мусор. Вам нужна инициализация:

struct event_cb callback;
callback.cb = my_event_cb;
callback.data = 42;

или некоторые такие вещи.

Ответ 5

Регистрация обратного вызова означает, что вы указываете, какую функцию следует вызывать при возникновении события интереса. В основном вы устанавливаете указатель функции при регистрации обратного вызова.

Ответ 6

Вы создали указатель объявленной структуры, но он не указывает ни на что:

struct event_cb *callback;

Вы должны просто создать тип своей структуры:

struct event_cb callback;

а затем передать свой адрес в функции.