1

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.

ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • What is wrong with using a `va_list`? – Cheatah Jun 28 '22 at 06:02
  • Show how you would like to use this. So let's say you want to call either `printf` or `syslog` from a dynamically loaded function. How would the caller know which arguments to pass? – Cheatah Jun 28 '22 at 06:10
  • The function that I'm calling doesn't take a `va_list`. – ideasman42 Jun 28 '22 at 06:11
  • @cheatah added the function I'm wrapping. – ideasman42 Jun 28 '22 at 06:12
  • 2
    I think the links you included lay out all the options that there are. If none of them work for your application, then you are out of luck. So this question is really a duplicate. But for instance, did you see [my answer about `__builtin_apply`](https://stackoverflow.com/a/61545790/634919)? – Nate Eldredge Jun 28 '22 at 06:13
  • Some compilers or libraries (e.g. [GCC](https://gcc.gnu.org/)...) could be helpful. You might also use [GNU lightning](https://www.gnu.org/software/lightning/) – Basile Starynkevitch Jun 28 '22 at 06:14
  • @nate-eldredge I checked using `__builtin_apply` and it seems that varargs do not work with this. – ideasman42 Jun 28 '22 at 06:16
  • So use `wl_proxy_marshal_array_flags`. – KamilCuk Jun 28 '22 at 06:16
  • @KamilCuk the code generated by wayland-scanner uses `wl_proxy_marshal_flags`. – ideasman42 Jun 28 '22 at 06:17
  • @ideasman42: varargs do work with `__builtin_apply` in general, e.g. the example I included in the post does work for me. That is one of the main reasons for it to exist. – Nate Eldredge Jun 28 '22 at 06:18
  • @nate-eldredge your right `__builtin_apply` does work (somehow it was failing when I first tried using it). This could be included as an answer. – ideasman42 Jun 28 '22 at 06:40
  • In case the variadic arguments are all of the same type, there are some possible macro tricks. (Although using variadic functions is horrible design to begin with, it's such a pointless feature.) – Lundin Jun 28 '22 at 06:56
  • So something generates C code that calls `wl_proxy_marshal_flags`. Are you planning to write your own implementation of this function that dlopens the wayland library, dlsyms the real function, and forwards arguments to it? – n. m. could be an AI Jun 28 '22 at 10:25

3 Answers3

0

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;
}
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396
0

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

Good 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);
}
Lorinczy Zsigmond
  • 1,749
  • 1
  • 14
  • 21
  • An issue with using `wl_proxy_marshal_array_flags` is the calls to `wl_proxy_marshal_flags` are from source files generated by `wayland-scanner`, so the source generator would need to be updated, or some non-trivial post-processing would need to be performed on the source. – ideasman42 Jun 29 '22 at 00:40
  • 1
    You can try and insert the quoted `wl_proxy_marshal_flags` implementation into your code, replacing `wl_proxy_marshal_array_flags` to `(*ptrto_wl_proxy_marshal_array_flags)` – Lorinczy Zsigmond Jun 30 '22 at 04:17