Home > Enterprise >  How write a dynamic loader that wraps C variadic functions?
How write a dynamic loader that wraps C variadic functions?

Time:06-29

In order to avoid depending on a library at run-time I have written a dynamic loader that uses dlopen / dlsym to load functions form a library at run-time.

To link at build time I use wrapper functions which call into the function pointers set by dlsym.

I've run into a problem with variadic functions, where there doesn't seem to be a way to dynamically load the function and have it forward the variadic arguments.

Is there a way to write a wrapper library that supports varargs that doesn't...

  • Depend on knowing the number of variadic arguments or ending the arguments with a sentinel value.
  • Depend on working around the problem by changing the code which calls into the variadic function (which I happen not to have control over in this case).

It seems like this might be supported but most existing answers suggest to workaround the problem in a way that isn't practical in my use case.

For context the function signature I'm wrapping is:

struct wl_proxy *wl_proxy_marshal_flags(
        struct wl_proxy *proxy,
        uint32_t opcode,
        const struct wl_interface *interface,
        uint32_t version,
        uint32_t flags,
        ...);

This function is called by generated code (see wayland-scanner), although I rather not make this question specifically about Wayland.


Note that similar questions have been asked already, such as:

But they suggest alternatives such as using vfprintf which don't work in my use case.

CodePudding user response:

The usual way is to make the variadic function a mere interface on a real function taking a va_list as a parameter. This pattern (which is the way fprintf and vfprintf work in the Standard Library) provides a smooth way to forward a dynamic number of arguments by calling directly the function using the va_list.

Here you could write:

struct wl_proxy *vwl_proxy_marshal_flags(
        struct wl_proxy *proxy,
        uint32_t opcode,
        const struct wl_interface *interface,
        uint32_t version,
        uint32_t flags,
        va_list params) {
    // actual code
    ...
}
struct wl_proxy *wl_proxy_marshal_flags(
        struct wl_proxy *proxy,
        uint32_t opcode,
        const struct wl_interface *interface,
        uint32_t version,
        uint32_t flags,
        ...) {
    va_list params
    va_start(params, flags);
    struct wl_proxy *cr = vwl_proxy_marshal_flags(proxy, opcode,
        interface, version, flags, params);
    va_end(params);
    return cr;
}

CodePudding user response:

Assuming all items in the variadic argument list are of the same type, then you can use this trick:

Implement the actual function as a non-variadic one, taking an array and size. As well as any number of non-variadic arguments. Example:

void actual_func (int this, double that, size_t argc, int argv[argc])
{
  printf("this:%d that:%f\n", this, that);
  for(size_t i=0; i<argc; i  )
    printf("%d ", argv[i]);
}

Here this and that can be any parameters of any type, corresponding to the fixed parameters in your examples. The argc and argv is the size and array respectively.

We can then write a variadic macro to translate the variadic call into a plain function call:

#define COUNT_ARGS(...) ( sizeof((int[]){__VA_ARGS__}) / sizeof(int) )
#define func(this,that,...) actual_func(this, that, COUNT_ARGS(__VA_ARGS__), (int[]){__VA_ARGS__})

The helper macro COUNT_ARGS counts the number of variadic items - they all have to be int or it won't work. This gives the array size. Then a temporary array in the form of a compound literal is initialized with the variadic arguments, then passed to the function. Full example:

#include <stdio.h>

void actual_func (int this, double that, size_t argc, int argv[argc])
{
  printf("this:%d that:%f\n", this, that);
  for(size_t i=0; i<argc; i  )
    printf("%d ", argv[i]);
}

#define COUNT_ARGS(...) ( sizeof((int[]){__VA_ARGS__}) / sizeof(int) )
#define func(this,that,...) actual_func(this,that,COUNT_ARGS(__VA_ARGS__),(int[]){__VA_ARGS__})

int main (void) 
{
  func(1, 1.0f, 1, 2, 3);
}

This has the same type safety as integer assignment, meaning so-so, but it is way safer than variadic functions.

CodePudding user response:

Bad news: I don't think there is an universal (platform-independent) solution.

Goot news: Wayland's developers seem to have thought of this problem, that's why they created _array_ functions, e.g. in wayland-client-core.h:

struct wl_proxy *
wl_proxy_marshal_flags(struct wl_proxy *proxy, uint32_t opcode,
                       const struct wl_interface *interface,
                       uint32_t version,
                       uint32_t flags, ...);

struct wl_proxy *
wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode,
                             const struct wl_interface *interface,
                             uint32_t version,
                             uint32_t flags,
                             union wl_argument *args);

The former function calls the latter:

WL_EXPORT struct wl_proxy *
wl_proxy_marshal_flags(struct wl_proxy *proxy, uint32_t opcode,
                       const struct wl_interface *interface, uint32_t version,
                       uint32_t flags, ...)
{
        union wl_argument args[WL_CLOSURE_MAX_ARGS];
        va_list ap;

        va_start(ap, flags);
        wl_argument_from_va_list(proxy->object.interface->methods[opcode].signature,
                                 args, WL_CLOSURE_MAX_ARGS, ap);
        va_end(ap);

        return wl_proxy_marshal_array_flags(proxy, opcode, interface, version, flags, args);
}
  • Related