Как разрешить пользователю привязать функцию обработчика кликов к кнопке?

#c #variadic-functions

#c #переменные функции

Вопрос:

В настоящее время я создаю свой собственный графический интерфейс, используя библиотеку SDL2 для программы, которую я написал на C (c89).

Проблема

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

Что я пробовал

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

 bind_ClickHandler(void (*function)(void)) {/* bind it to a button */}
  

но, очевидно, это слишком ограничивает пользователя.

Итак, я попытался использовать stdarg.h :

 /* ... : arguments to pass to the click handler */
bind_ClickHandler(void (*function)(), int nb_args, ...) 
{ 
     void *arg_pt;
     va_list args;
     va_start(args, nb_args);
     /* I don't know what to do so i cast it as a garbage pointer */
     arg_pt = va_arg(args, void *);
     
     /* repeat this in a loop to save the args and bind the click handler to a button */ 
}
  

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

Чего я хочу

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

Ответ №1:

Просто передайте один void* указатель. Например, qsort_r или pthread_create [*]. Пользователю нужен только один указатель — и этот указатель будет указывать куда угодно, на что захочет пользователь.

 void (*clickhandler_user_function)(void *) = NULL;
void *clickhandler_user_function_arg = NULL;
void bind_ClickHandler(void (*function)(void*arg), void *function_arg) {
    // store function and fucntion_arg somewhere
    clickhandler_user_function = function;
    clickhandler_user_function_arg = function_arg;
}
  

Достаточно одного указателя:

 struct user_context_s {
    int some_number;
    const char *some string;
    // etc.
};
void user_function_that_operates_on_user_context(void *p) {
   struct user_context_s *ctx = p;
   printf("My number is: %dn", ctx->some_number);
}
int main() {
    // user can use malloc() and manage the lifetime of it's context
    struct user_context_s ctx = {1, "blabla"};
    bind_ClickHandler(user_function_that_operates_on_user_context, amp;user_context);
}
  

[*] — или thrd_create или fopencookie использует void *cookie или стандарт sigevent использует void *sival_ptr; in sigval для передачи контекста, когда sigev_notify == SIGEV_THREAD .

Комментарии:

1. Итак, я должен просто позволить пользователю передать указатель на аргументы и вызвать обработчик кликов с этим указателем? Это здорово, но для этого пользователю необходимо разработать свою функцию, чтобы использовать этот указатель, если он хочет использовать графический интерфейс.

2. i have just to let the user pass a pointer to the args and i call the click handler with this pointer ? ДА. but it requires the user to design its function to use this pointer if he wants to use the GUI ДА. C не имеет замыканий .

3. Мне нравится ваше решение, потому что оно позволяет снизить сложность с моей стороны, но я хотел бы знать, есть ли способ разрешить пользователю передавать аргументы моей функции bind , как если бы он вызывал обработчик кликов.

4. Нет. C не имеет замыканий. Именно для этого в C существуют std::bind и std::function и лямбды. Вы могли бы реализовать это, заранее зная тип и количество аргументов, передаваемых функции, и выделив память для хранения значений аргументов. И это то, для чего template предназначен C и что std::bind на самом деле делает. Это C. C не имеет шаблонов. C не имеет замыканий.