переменная функция приведена в макросе C - PullRequest
0 голосов
/ 24 января 2019

Я хочу поместить этот блок кода в макрос C:

void* instance = frm_config_get_instance("inside");
int (*func)(void* inst, double value) = frm_config_get_function("comp123_work2");
int res = func(instance, 0);

, чтобы я мог использовать его так:

res = FRM_CONFIG_CALL("inside", "comp123_work2", 0.0)

это то, что я смог придуматьпока что:

#define FRM_CONFIG_CALL(instance, function, ...) \
  ((int*)(void*, __VA_ARGS__))frm_config_get_function(function) \
  (frm_config_get_instance(instance), __VA_ARGS__)

есть идеи?

Ответы [ 3 ]

0 голосов
/ 24 января 2019

Подход, который вы пытаетесь реализовать, означает, что программист должен заранее знать число и тип параметров каждой «динамически вызываемой» функции, и это лишает смысла наличие такого механизма в первую очередь.Например, вы должны знать, что comp123_work2 принимает один параметр типа double.

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

Существует несколько подходов для достижения того, что вы делаете сейчас:

1.Передайте параметры как void*

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

Распространенный способиметь дело с различными типами параметров в C, это просто передать свои пользовательские параметры через указатель void, то есть:

typedef int(*voidvoidfunc_t)(void*, void*);
extern void* frm_config_get_instance(const char* name);
extern voidvoidfunc_t frm_config_get_function(const char* name);

И тогда каждая реализация может привести к тому, что она хочет:

int printf_wrapper(void * ch, void * p)
{
    double val = *(double*)p;
    return printf(ch, val);
}

voidvoidfunc_t frm_config_get_function(const char* name)
{
    return printf_wrapper;
}

И вы бы просто назвали его, используя:

void * parameter = frm_config_get_instance("inside");
voidvoidfunc_t func = frm_config_get_function("comp123_work2");
double val = 0.0;
int result = func(parameter, &val);

2.Указатели на функции переменных переменных

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

typedef int(*varargfunc_t)(void*, ...);
extern void* frm_config_get_instance(const char* name);
extern varargfunc_t frm_config_get_function(const char* name);

И затем:

void * parameter = frm_config_get_instance("inside");
varargfunc_t func = frm_config_get_function("comp123_work2");
int result = func(parameter, 0.0);

Но тогда все ваши "рабочие" функции должны будут "распаковывать "args каждый раз, что-то вроде:

int printf_wrapper(void * ch, ...)
{
    va_list va;
    va_start(va, ch);
    int ret = vfprintf(stdout, ch, va);
    va_end(va);
    return ret;
}

varargfunc_t frm_config_get_function(const char* name)
{
    return printf_wrapper;
}

3.Наличие отдельной функции для каждого типа параметра

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

typedef int(*intfunc_t)(void*, int);
typedef int(*floatfunc_t)(void*, float);
typedef int(*stringfunc_t)(void*, const char*);

Тогда вы не можете неправильно интерпретировать аргументы:

int printf_wrapper(void * ch, float val)
{
    return printf(ch, val);
}

floatfunc_t frm_config_get_float_function(const char* name)
{
    return printf_wrapper;
}

Это означает, что у вас есть строго типизированный указатель fn после вызова frm_config_get_xxxxx_function:

void * parameter = frm_config_get_instance("inside");
floatfunc_t func = frm_config_get_float_function("comp123_work2");
int result = func(parameter, 0.0f);

4.Использование альтернативного типа объединения, такого как GValue в GLib

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

int printf_wrapper(void * ch, GValue *val)
{
    // this checks that the parameter has the correct type
    if (!G_VALUE_HOLDS_FLOAT(val))
        error();

    float f = g_value_get_float(val);
    return printf(ch, f);
}

typedef int(*gvalue_func_t)(void*, GValue *val);
extern void* frm_config_get_instance(const char* name);
extern gvalue_func_t frm_config_get_function(const char* name);

И затем вы создаете объект GValue во время выполнения:

void * parameter = frm_config_get_instance("inside");
gvalue_func_t func = frm_config_get_function("comp123_work2");

GValue val = G_VALUE_INIT;
g_value_set_float(&val, 0.0f);
func(parameter, &val);
0 голосов
/ 30 января 2019

Во-первых, как было сказано ранее: это, как говорится, выглядит странным запросом.

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

Предложения / думает, что стоит рассмотреть:

Используйте сгенерированный код или макросы для определения ваших функций, чтобы вы могли «искать» шаблон функции, а не выводить его.

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

  • упаковать / сериализовать ваши аргументы в контейнер

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

Это может выглядеть для вас похоже.Он не дает прямого ответа на ваш вопрос, так как вам все еще нужен способ подачи аргументов, но что-то вроде:

struct arglist = al;
int rv;

al.feed_double(0.0);
al.feed_int(4);

FUNC_MAGIC("foo", "bar", al, rv);

assert(rv != NOT_VALID_ARG_PACKING);

Было бы достижимо.

Если перейти от A_MACRO (0.0, 4)версия канала возможна с помощью va_list, и вызов функции, которая также понимает, знает значение "foo".Вы можете использовать это, чтобы вернуть свой oneliner CallSite, но вам понадобится фрагмент кода, который выглядит примерно так:

int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
    // ...
    elif (!strcmp("foo", func) {
        va_start(vl, 2); // I just knew "foo needed 2 args"
        tmp_dbl = va_arg(vl, double);
        al_tmp.pack_double(tmp_dbl);

        tmp_int = va_arg(vl, int);
        al_tmp.pack_int(tmp_int);
    }
}

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

0 голосов
/ 24 января 2019

То, что вы собираетесь сделать, вероятно, не сработает. Но вы можете создать его «облегченную» версию:

#define FRM_CONFIG_FUNC(function, ...) \
  ((int(*)(void*, __VA_ARGS__))frm_config_get_function(function))

#define FRM_CONFIG_CALL(func, instance, ...) \
  (func)(frm_config_get_instance(instance), __VA_ARGS__)

и используйте его как

res = FRM_CONFIG_CALL(FRM_CONFIG_FUNC("comp123_work2", double), "inside", 0.0)

Отказ от ответственности: я не проверял, но это может дать вам идею.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...