Perform scheduledownload and create a logger. The structure of this logger is as follows:
-
Code: select all
-
#ifdef _DEBUG
#define LOGGER(log_level, filename, line, format, ...) \
logger_action(log_level, filename, line, format, __VA_ARGS__);
#else
#define LOGGER
#endif/*
* Log the strings. filename should be __FILE__ and line should be __LINE__
*/
void logger_action(LOG_LEVEL log_level, LPCTSTR filename, int line, LPCTSTR format, ...);
#define UTILS_RVIF_WITH_LOG(expr, val, log_level, filename, line, format, ...) \
if (!(expr)) { \
LOGGER(log_level, filename, line, format, __VA_ARGS__); \
return val; \
}
It is clear that the real function is logger_action, and the two macros are packaged separately. Here:
1. In macro definition, _ va_args _ is used to represent variable parameters. You can just use. If the variable parameter is null, a comma (,) will be generated in theory, leading to compilation failure (one comma after the format parameter). At this time, __va_args _ will automatically remove unnecessary commas. This is the action of the VC compiler. If it is the GNU Compiler, you need to write #__ va_args __, and use ## to remove unnecessary commas, but here we will remove the redundant commas ). _ Va_args _ is the keyword specified in the c99 specification. In VC, it must be supported in Visual Studio 2005.
2. _ va_args _ cannot appear in function implementation, but can only appear in macros. Therefore, this brings about a problem: In logger_action, we don't really want to analyze the format and variable parameters. We just want to pass these parameters to stringcchprintf, so I tried to process it like this in logger_action:
-
Code: select all
-
// handle format & args
va_list args;
va_start(args, format);
UTILS_RETURN_IF_FAIL(SUCCEEDED(StringCchPrintf(log_str_buf + log_str_cur_index, _countof(log_str_buf) - log_str_cur_index, format, args)));
va_end(args);
Va_list is a char *, and va_start is a macro. It sets the ARGs parameter to the format parameter address + the number of bytes of the format parameter. In other words, set ARGs to the position after the format in the function stack, so that ARGs points to the beginning of the variable parameter. The va_arg parameter can be used to retrieve a variable parameter. This is also why the va_arg macro provides a parameter type: va_arg determines the number of bytes to be retrieved Based on the parameter type. The final va_end is to set ARGs to null.
Therefore, va_list/va_start/va_arg/va_end is actually very simple. It is a pointer operation that extracts uncertain parameters from the function stack. Here we only need to let stringcchprintf handle it, so I naively passed The args parameter to stringcchprintf.
The result is: there are no compilation errors and execution errors. stringcchprintf generates a bunch of messy strings. Start Debug. By observing the function stack, the passed variable parameters are OK, which proves that _ va_args _ is passed between a bunch of macros. Why cannot stringcchprintf obtain these variable parameters? In fact, it is very simple:
As mentioned above, when a function with variable parameters is processed, it uses va_list/va_start... these macros are pointer operations on the stack of the function. When we call stringcchprintf, the variable parameter is passed into args, in fact, The args type is char *, which is an address and does not represent the heap of variable parameters. The unique parameter that stringcchprintf can fetch is args. The value in it is the stack address after the value of the logger_action Function Format parameter !! It's a natural error. It's good if there's no crash.
OK. What should I do? Conclusion:
1. In the logger_action function, you can extract variable parameters one by one and press these parameters one by one into the stack of stringcchprintf. This practice is very portable, and different compilers and platforms may have problems, because it involves compilation.
2. Actually, we call sprintf in mysprintf. Unless method 1 is used, it cannot be implemented. Fortunately, sprintf has a brother named vsprintf. This function with V is not receiving at last... but accept a va_list parameter. That is to say, the difference between vsprintf and sprintf is that it is not a variable parameter in its own stack, instead, we can find variable parameters on the given va_list parameter address. Great! So find out if stringcchprintf has such a brother-yes! Stringcchvprintf. So the Code only needs to modify one character and it will be OK:
-
Code: select all
-
// handle format & args
va_list args;
va_start(args, format);
UTILS_RETURN_IF_FAIL(SUCCEEDED(StringCchVPrintf(log_str_buf + log_str_cur_index, _countof(log_str_buf) - log_str_cur_index, format, args)));
va_end(args);
This is OK! Stringcchvprintf starts at the address specified by the ARGs parameter and finds the corresponding parameter according to the definition in the format. The test passes and the program works normally.
In summary, there are two main points:
1. _ va_args _ cannot appear in a function. It can only be used in a macro.
2. To pass variable parameters in a function, check whether the passed function has a version of the va_list parameter. Otherwise, it will be very troublesome.