Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

variadic function cast in C macro

Tags:

c

I want to get this code block into a C macro:

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

so I can use it like this:

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

this is what I was able to come up with so far:

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

any ideas?

like image 424
moin moin Avatar asked Jul 28 '26 07:07

moin moin


1 Answers

The approach you are trying to implement means that the programmer must know the number and type of parameters of each "dynamically invokable" function in advance, and this defeats the point of having such mechanism in the first place. For example, you must know that comp123_work2 accepts a single parameter of type double.

While this violation of OOP principles might not bother you that much, in practice this means you will trade compile errors for hard-to-find runtime issues. Reading an incorrect varargs value, passed to a function through a macro, is a disaster waiting to happen.

There are several approaches you could take to achieve what you are doing now:

1. Pass parameters as void*

This approach doesn't give much more compile time safety, but it's far less surprising to an unsuspecting maintainer of your code.

A common way to deal with different parameter types in C is to simply pass your custom parameters through a void pointer, i.e.:

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

And then each implementation can cast to whatever it wants:

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

And you would simply call it using:

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. Variable-args function pointers

Also, note that it's also possible to define a variable arguments function pointer, if all your functions used varargs. Again, no compile time safety (as with any variable args function in 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);

And then:

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

But then all your "worker" functions will have to "unpack" args each time, something like:

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. Having a separate function for each parameter type

If you don't have that many parameters, a way to achieve compile-time type safety would be using separate functions for each param type, i.e.:

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

Then you are safe from interpreting the args in a wrong way:

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

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

Which means you have a strongly typed fn pointer after the call to 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. Using a variant union type like GValue in GLib

This is perhaps slighly less common and doesn't provide full type safety, but it at least provides metadata which can help you throw an error in case of an incorrect invocation. The idea is to have a union of several values with a value type, so that your function can check if the parameter matches its expectation.

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

And then you construct the GValue object at run time:

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);
like image 141
Groo Avatar answered Jul 30 '26 01:07

Groo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!