Я пытаюсь написать оболочку для функции с переменным числом в стиле C, такой как printf, которая добавляет дополнительные аргументы, но у меня возникли проблемы с этим:
void printf(char* fmt, ...); // the thing I'm trying to wrap
void wrapper(char* fmt, ...)
{
printf(fmt, extra_arg1, extra_arg2, /* the variadic arguments */);
}
но что я пишу для /* the variadic arguments */
?
Даже если у функции, которую я пытаюсь обернуть, есть версия, которая требует va_list
Я не могу это сделать:
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
а также args
не волшебным образом превращаться в va_list
тот vprintf
надеется.
Я знаю, что мог бы написать макрос и использовать __VA_ARGS__
:
void printf(char* fmt, ...); // the thing I'm trying to wrap
#define wrapper(fmt, ...) printf(fmt, extra_arg1, extra_arg2, __VA_ARGS__)
но я пытаюсь избежать этого и написать обертку как функцию. Есть способ сделать это?
(Кстати, я не могу использовать вариабельные шаблоны C ++ 11).
Я не вижу ничего плохого в использовании макросов, но это только я 🙂
В любом случае, есть способ сделать это.
Если вас не волнует переносимость, вы можете использовать встроенную сборку.
Функции Variadic используют соглашение о вызовах cdecl, что означает, что вызывающая сторона отвечает за передачу аргументов в стек, а затем за очистку стека после возврата вызываемого.
Таким образом, простой вызов printf
printf("%d, %d, %d", 1, 2, 3)
будет выглядеть примерно так:
char format[] = "%d, %d, %d";
__asm {
push 3
push 2
push 1
lea eax, [format]
push eax
call printf
add esp,10h
}
Обратите внимание, что последний нажатый параметр используется va_start
получить адрес в стеке и va_arg
просто перебирайте аргументы.
Вы можете использовать то же поведение и снова выдвинуть аргументы variadic:
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
}
}
Обратите внимание, что вы должны заранее знать, сколько аргументов в стеке и размер каждого аргумента (printf вычисляет это из строки формата).
Вы можете имитировать printf и извлекать соответствующую информацию из строки формата (или любой другой функции, которую вы переносите).
Теперь, если у вас нет никаких знаний о типах, вы все равно можете сойти с рук
освободив место в стеке для ваших переменных и скопировав память (из стека в стек):
__asm {
push extra2
push extra1
sub esp, total-bytes-needed
}
memcpy(esp, block-of-memory, size-of-block);
Это довольно громоздкое и не переносимое решение, но оно должно работать.
Надеюсь, это поможет!
Других решений пока нет …