我有两个函数,它们都有相似的参数
void example(int a, int b, ...);
void exampleB(int b, ...);
现在example
调用exampleB
,但是我如何在不修改exampleB
的情况下传递可变参数列表中的变量(因为它已经在其他地方使用了)。
我有两个函数,它们都有相似的参数
void example(int a, int b, ...);
void exampleB(int b, ...);
现在example
调用exampleB
,但是我如何在不修改exampleB
的情况下传递可变参数列表中的变量(因为它已经在其他地方使用了)。
va_list
的函数:#include <stdarg.h>
static void exampleV(int b, va_list args);
void exampleA(int a, int b, ...) // Renamed for consistency
{
va_list args;
do_something(a); // Use argument a somehow
va_start(args, b);
exampleV(b, args);
va_end(args);
}
void exampleB(int b, ...)
{
va_list args;
va_start(args, b);
exampleV(b, args);
va_end(args);
}
static void exampleV(int b, va_list args)
{
...whatever you planned to have exampleB do...
...except it calls neither va_start nor va_end...
}
, ...
。如果您已经将可变参数转换为va_list
,则可以将va_list
传递给另一个只接受va_list
的函数,但是该函数(或其调用的函数)必须知道va_list
中包含什么内容。 - Jonathan Leffler也许这有点像在池塘中扔石头,但是它似乎可以很好地与C++11可变参数模板一起使用:
#include <stdio.h>
template<typename... Args> void test(const char * f, Args... args) {
printf(f, args...);
}
int main()
{
int a = 2;
test("%s\n", "test");
test("%s %d %d %p\n", "second test", 2, a, &a);
}
至少它可以与g++
一起工作。
args...
会扩展包。 - Swordfish你应该创建那些接受 va_list 参数的函数,并将它们传递进去。可以参考 vprintf
函数作为示例:
int vprintf ( const char * format, va_list arg );
我也想包装printf,找到了这里的一个有用的答案:
我对性能一点都不感兴趣(我确信这段代码可以在许多方面得到改进,随意进行 :)),这只是为了一般的调试打印,所以我这样做:
//Helper function
std::string osprintf(const char *fmt, ...)
{
va_list args;
char buf[1000];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args );
va_end(args);
return buf;
}
然后我可以像这样使用它
Point2d p;
cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";
c++的输出流在某些方面非常美丽,但实际上,如果你想打印像这样的一些小字符串(如括号、冒号和逗号)插入到数字之间,它们就变得可怕了。
#define exampleB(int b, ...) example(0, b, __VA_ARGS__)
可能不是完全相同的情况,但如果您要为字符串格式化函数(例如日志记录器)定义包装器:
void logger(const char *name, const char *format, ...);
void wrapper(const char *format, ...);
logger
的wrapper
时,我们可以首先使用vasprintf创建一个字符串,然后将其传递给logger
。#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
static void wrapper(const char *format, ...)
{
char *string;
va_list args;
va_start(args, format);
// variadic printf with allocated string. must free()
vasprintf(&string, format, args);
logger("wrapper", "%s", string);
free(string);
va_end(args);
}
虽然不是最干净的方法,但可以使用。当你必须避免使用macro函数时,请尝试这个方法。
vasprintf
的返回值。 - guan boshen顺便提一下,许多C语言实现都有一个内部的v?printf变体,我认为这应该成为C标准的一部分。具体细节因实现而异,但典型的实现将接受一个包含字符输出函数指针和信息的结构体,以说明应该发生什么。这使得printf、sprintf和fprintf都可以使用相同的“核心”机制。例如,vsprintf可能是这样的:
void s_out(PRINTF_INFO *p_inf, char ch)
{
(*(p_inf->destptr)++) = ch;
p_inf->result++;
}
int vsprintf(char *dest, const char *fmt, va_list args)
{
PRINTF_INFO p_inf;
p_inf.destptr = dest;
p_inf.result = 0;
p_inf.func = s_out;
core_printf(&p_inf,fmt,args);
}
这是唯一的方法..也是最好的方法..
static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz
BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
BOOL res;
va_list vl;
va_start(vl, format);
// Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
printf("arg count = %d\n", argCount);
// ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
__asm
{
mov eax, argCount
test eax, eax
je noLoop
mov edx, vl
loop1 :
push dword ptr[edx + eax * 4 - 4]
sub eax, 1
jnz loop1
noLoop :
push format
push variable1
//lea eax, [oldCode] // oldCode - original function pointer
mov eax, OriginalVarArgsFunction
call eax
mov res, eax
mov eax, argCount
lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
add esp, eax
}
return res;
}
call eax
调用所有已推入的 push,其中 eax
是 OriginalVarArgumentsFunction
地址。这样,您就可以使用任意数量的参数来使用 ...
,并且仍然能够在钩子中使用它。 - SSpokeOriginalVarArgsFunction(variable1, format, ...);
这就是为什么这个修复程序可以解决问题,仅此而已。 - SSpoke使用GNU C扩展:
int FIRST_FUNC(...){
__builtin_return(
__builtin_apply(
(void(*)())SECOND_FUNC, __builtin_apply_args(), 100));
}
根据您包装vsprintf
的评论,以及这被标记为C++,我建议不要尝试这样做,而是改变您的接口以使用C++ iostreams。它们比print
函数系列具有优势,例如类型安全性和能够打印printf
无法处理的项目。现在进行一些重构可能会在未来节省大量痛苦。