2

I'm trying to write a wrapper for a C-style variadic function like printf, which adds some extra arguments, but I'm having trouble with it:

void printf(char* fmt, ...);  // the thing I'm trying to wrap

void wrapper(char* fmt, ...)
{
    printf(fmt, extra_arg1, extra_arg2, /* the variadic arguments */);
}

but what do I write for /* the variadic arguments */?

Even if the function I'm trying to wrap has a version that takes a va_list, I can't do it:

void vprintf(char* fmt, va_list args);

void wrapper(char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, extra_arg1, extra_arg2, args);
    va_end(args);
}

extra_arg1, extra_arg2 and args don't magically turn into a va_list that vprintf expects.

I know I could write a macro and use __VA_ARGS__:

void printf(char* fmt, ...);  // the thing I'm trying to wrap

#define wrapper(fmt, ...) printf(fmt, extra_arg1, extra_arg2, __VA_ARGS__)

but I'm trying to avoid that and write the wrapper as a function. Is there a way to do that?

(By the way, I can't use C++11 variadic templates either.)

tshepang
  • 12,111
  • 21
  • 91
  • 136
HighCommander4
  • 50,428
  • 24
  • 122
  • 194
  • Have you already looked here: http://stackoverflow.com/a/150616/2036917 – Udo Klein Oct 15 '13 at 17:29
  • @UdoKlein: That questions just suggests using `va_list`. I already know about that, the problem is I needed to **add** arguments to it, and I don't know how to do that. – HighCommander4 Oct 15 '13 at 17:34
  • I don't really understand why you would need to use a single `vprintf` statement. As far as I can see, the extra args for your `wrapper` are also just variable to be printed correct? Why could you then not use separate print statements, or better just include them in the format of the `wrapper` call? – Pankrates Oct 15 '13 at 17:43
  • @Pankrates: The actual function I'm wrapping is not `vprintf`, and it does not have the property that two consecutive calls have the same effect as a single call with the combined arguments. – HighCommander4 Oct 15 '13 at 17:48
  • 1
    In general, you can't add args to a va_list. It is an opaque type, and the only operations it supports are va_start to initialize it, and va_end to indicate that you are done with it (and maybe va_copy). Now, if you find the implementation of va_start, maybe you can come up with a clever way to add args, but that probably breaks the warranty :) – Markku K. Oct 15 '13 at 18:01
  • Looking for _some_ way to simplify the problem. 1) Are `extra_arg1, extra_arg2` various types or do you have a single type in mind? 2) Are the types in `...` various or a single type? 3) Are `extra_arg1, extra_arg2` the same type as `...`? 4) Is the count of args discernible from `...`? – chux - Reinstate Monica Oct 15 '13 at 18:23
  • @HighCommander4, Failed to find a general solution other than OP's macro idea. To do as a function, it appears that the `...` arguments need to be parsed, pre-pended with `extra_arg1, extra_arg2` and then, as a group, passed to the "wrapped" function. Without knowing the types and count of args in `...`, can't parse the `...`. – chux - Reinstate Monica Oct 15 '13 at 18:42
  • @chux: 1) Various types. 2) Various types. 3) No. 4) No. Guess that doesn't simplify anything, sorry :) – HighCommander4 Oct 15 '13 at 18:44
  • I guess that's partially why people invented `VARIANT` type and passing parameters as `void*[]`. – Joker_vD Nov 21 '13 at 22:41
  • It is curious that people have to provide a format string that expects extra arguments compared to the list of values passed with it. Normally, you'd get around the problem by having the `wrapper()` function take a `printf()`-conformant format string and argument list, and in the wrapper, you'd use `printf(fmt2, extra_arg1, extra_arg2);` and then `vprintf(fmt, args);` – Jonathan Leffler Nov 21 '13 at 23:00

1 Answers1

0

I don't see anything wrong with using macros, but that's just me :)

Any way, there is a way of doing it. If you don't care about portability you can use inline assembly. Variadic functions are using cdecl calling convension, which means that the caller responsible to push the arguments onto the stack and than clean the stack after the callee returns. thus, a simple printf call

printf("%d, %d, %d", 1, 2, 3)

will look something like:

char format[] = "%d, %d, %d";
__asm {
  push 3
  push 2
  push 1
  lea  eax, [format]
  push eax
  call printf
  add  esp,10h
}

Note that the last param pushed is used by va_start to get the address on stack and va_arg just iterate through the arguments.

You can use the same behavior and push the variadic arguments again:

char new_format[] = "%d, %d, %d, %d, %d";
wrapper(new_format, ...) {
  char *va = addressof(new_format)+sizeof(char*);  // first argument after new_format
  int i;

  __asm {
    push extra2
    push extra1
  }
  for(i=0; i < num_of_arguments; i++)
     __asm push, va += sizeof(argument)
  // we just pushed [3, 2, 1] in our example
  __asm {
    lea  eax, [new_format]
    push eax
    call printf
    add  esp,18h   // <-- We now have to discard more stuff on the stack
  }
}

Note that you must know in advance how many arguments are on the stack and the size of each argument (printf calculate this from the format string).

you can mimic printf and extract the relevant information from the format string (or any other function you're wrapping).

Now, In case you don't have any knowledge about the types, you can still get away with it by making room on the stack for your variables and copy the memory (from stack to stack):

__asm {
  push extra2
  push extra1
  sub esp, total-bytes-needed
}
memcpy(esp, block-of-memory, size-of-block);

This is fairly cumbersome and not portable solution but it should work.

Hope that helps!

Yuval
  • 907
  • 9
  • 24