I'm having a problem with va_ methods, and I couldn't find an example (or didn't figure out what the keywords are). The problem is, I need to use the same args for different formats, but the compiler gives me:
incorrect usage of va_start
error. The code I'm trying is something like this:
void vSaveNecessaryFields(EnumA eFormat, ...)
{
va_list xArgs, xArgs2;
const char *fmt1 = NULL, *fmt2 = NULL;
char caString[100] = {0};
fmt1 = cpGetDbFormat(eFormat);
fmt2 = cpGetPrinterFormat(eFormat);
va_start(xArgs1, fmt1);
va_copy(xArgs2, xArgs1);
vsnprintf(caString, sizeof(caString), fmt1, xArgs1);
vSaveToDb(caString);
va_start(xArgs2, fmt2);
vsnprintf(caString, sizeof(caString), fmt2, xArgs2);
vPrintFormatted(caString);
va_end(xArgs2);
va_end(xArgs1);
}
How can I solve this problem?
The argument to va_start should be the eFormat argument. Furthermore, the va_list is declared as xArgs but you use xArgs1, which causes a syntax error.
void vSaveNecessaryFields(EnumA eFormat, ...) {
va_list xArgs, xArgs2;
const char *fmt1 = NULL, *fmt2 = NULL;
char caString[100] = {0};
fmt1 = cpGetDbFormat(eFormat);
fmt2 = cpGetPrinterFormat(eFormat);
va_start(xArgs, eFormat);
vsnprintf(caString, sizeof(caString), fmt1, xArgs);
va_end(xArgs);
vSaveToDb(caString);
va_start(xArgs2, eFormat);
vsnprintf(caString, sizeof(caString), fmt2, xArgs2);
vPrintFormatted(caString);
va_end(xArgs2);
}
You need to call va_end, then call va_start a second time after you have closed the parameter block.
Related
So I have 2 functions that both have similar arguments
void example(int a, int b, ...);
void exampleB(int b, ...);
Now example calls exampleB, but how can I pass along the variables in the variable argument list without modifying exampleB (as this is already used elsewhere too).
You can't do it directly; you have to create a function that takes a 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...
}
Maybe throwin a rock in a pond here, but it seems to work pretty OK with C++11 variadic templates:
#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);
}
At the very least, it works with g++.
you should create versions of these functions which take a va_list, and pass those. Look at vprintf as an example:
int vprintf ( const char * format, va_list arg );
I also wanted to wrap printf and found a helpful answer here:
How to pass variable number of arguments to printf/sprintf
I was not at all interested in performance (I'm sure this piece of code can be improved in a number of ways, feel free to do so :) ), this is for general debugprinting only so I did this:
//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;
}
which I then can use like this
Point2d p;
cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";
The c++ ostreams are beautiful in some aspects, but practically the become horrific if you want to print something like this with some small strings such as parenthesis, colons and commas inserted between the numbers.
A possible way is to use #define:
#define exampleB(int b, ...) example(0, b, __VA_ARGS__)
It might not be exactly the same situation as described here, but if you were to define a wrapper for a string format function (e.g. logger):
void logger(const char *name, const char *format, ...);
void wrapper(const char *format, ...);
when you implement a wrapper that calls logger, we can just create a string first with vasprintf and then pass it to 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);
}
Not the cleanest, but works. Try this when you must avoid using macro functions.
Incidentally, many C implementations have an internal v?printf variation which IMHO should have been part of the C standard. The exact details vary, but a typical implementation will accept a struct containing a character-output function pointer and information saying what's supposed to happen. This allows printf, sprintf, and fprintf to all use the same 'core' mechanism. For example, vsprintf might be something like:
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);
}
The core_printf function then calls p_inf->func for each character to be output; the output function can then send the characters to the console, a file, a string, or something else. If one's implementation exposes the core_printf function (and whatever setup mechanism it uses) one can extend it with all sorts of variations.
Based on the comment that you're wrapping vsprintf, and that this is tagged as C++ I'd suggest not trying to do this, but change up your interface to use C++ iostreams instead. They have advantages over the print line of functions, such as type safety and being able to print items that printf wouldn't be able to handle. Some rework now could save a significant amount of pain in the future.
Adding ... to a parameter makes it include all the following parameters as well.
int FIRST_FUNC(int first_param...){
return SECOND_FUNC(first_param);
}
Or using GNU C extensions:
int FIRST_FUNC(...){
__builtin_return(
__builtin_apply(
(void(*)())SECOND_FUNC, __builtin_apply_args(), 100));
}
Using the new C++0x standard, you may be able to get this done using variadic templates or even convert that old code to the new template syntax without breaking anything.
This is the only way to do it.. and the best way to do it too..
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;
}
I have got a global function which has the following signature:
void Systemfehlerprotokollieren(BYTE quelle,WORD fehlercode,WORD subfehlercode,
BYTE klassifizierung, BYTE status,BYTE kanalnummer,DWORD detailfehler,
WORD modulnummer,WORD location,WORD wLenZusatzText,char *pcZusatztext);
This function I want to simplify in two ways.
Use variable arguments like in printf instead of pcZusatztext
And thus get rid of parameter wLenZusatzText
Use the original signature in a local context
So my external function (the last parameters) would look like:
ext_Systemfehlerprotokollieren(WORD location, char *form, ...);
this function then should call void Systemfehlerprotokollieren(. . . ) with its above mentioned parameters as before.
Right now I have the following code parts:
void vSystemfehlerprotokollierenText(BYTE quelle,WORD fehlercode,WORD subfehlercode,
BYTE klassifizierung,BYTE status,BYTE kanalnummer,DWORD detailfehler,
WORD modulnummer,WORD location,va_list args)
{
int ret;
char zepuf_printf_mode_lokal[ZELE];
memset(&zepuf_printf_mode_lokal[0],0x00,ZELE);
ret = vsnprintf_s(zepuf_printf_mode_lokal, ZELE-1,_TRUNCATE, "%s",args );
if (ret != -1)
{
if (ret < 0)
{
Systemfehlerprotokollieren(quelle,fehlercode,subfehlercode,
klassifizierung,status,kanalnummer,detailfehler,
modulnummer,location,0,NULL);
return;
}
}
Systemfehlerprotokollieren(quelle,fehlercode,subfehlercode,
klassifizierung,status,kanalnummer,detailfehler,
modulnummer,location,strlen(zepuf_printf_mode_lokal),zepuf_printf_mode_lokal);
}
in above function Systemfehlerprotokollieren will be called as usual
and
void SystemFehlerKG(WORD wFehlerCode, WORD wSubFehlerCode,BYTE KanalNummer,
DWORD detailinfo2, DWORD detailinfo3, WORD programmstelle, char *form, ... )
{
va_list args = NULL ;
if (form != NULL)
{
va_start(args, form);
vSystemfehlerprotokollierenText(SYS_FEHL_QUELLE_FLEXOS,wFehlerCode,wSubFehlerCode,
SYS_FEHL_KLASS_FEHLER, SYS_FEHL_STATUS_FEHLER_KOMMT_GEHT, KanalNummer,
(WORD)detailinfo2, (WORD)detailinfo3, programmstelle,args);
va_end(args);
}
else
vSystemfehlerprotokollierenText(SYS_FEHL_QUELLE_FLEXOS,wFehlerCode,wSubFehlerCode,
SYS_FEHL_KLASS_FEHLER, SYS_FEHL_STATUS_FEHLER_KOMMT_GEHT, KanalNummer,
(WORD)detailinfo2, (WORD)detailinfo3, programmstelle,"");
}
This works fine, when i call the second function like this:
SystemFehlerKG(5000+TCPIP_SYS_ERROR_FKT_SEND,0,SYS_FEHL_KANAL_ALLG,
iRet,0,SFPROG_00000,"%s","");
but I don't know what to change to get the same result with
SystemFehlerKG(5000+TCPIP_SYS_ERROR_FKT_SEND,0,SYS_FEHL_KANAL_ALLG,
iRet,0,SFPROG_00000);
... where the last two parameters are omitted...
[Edit #1]:
I see, that printf(); doesn't work either, it must be at least printf("");
so the closest possible approach would be
SystemFehlerKG(5000+TCPIP_SYS_ERROR_FKT_SEND,0,SYS_FEHL_KANAL_ALLG,
iRet,0,SFPROG_00000,"");
[Edit #2 as suggested by #Jan Krüger]:
void SystemFehlerKG(WORD wFehlerCode, WORD wSubFehlerCode,
BYTE KanalNummer, DWORD detailinfo2, DWORD detailinfo3,
WORD programmstelle, char *form, ... )
{
va_list args = NULL ;
va_start(args, form);
vSystemfehlerprotokollierenText(SYS_FEHL_QUELLE_FLEXOS,wFehlerCode,
wSubFehlerCode, SYS_FEHL_KLASS_FEHLER,
SYS_FEHL_STATUS_FEHLER_KOMMT_GEHT, KanalNummer,
(WORD)detailinfo2, (WORD)detailinfo3, programmstelle,args);
va_end(args);
}
I dont have the code at hand right now, but i think, that this threw an exception when form was not a valid (format) string (being NULL or "").
I will tell tomorrow.
[Edit #3:]
I made the changes to my code as in edit#2. Did not work.
But I found the error:
void vSystemfehlerprotokollierenText(BYTE quelle,WORD fehlercode,WORD subfehlercode,
BYTE klassifizierung,BYTE status,BYTE kanalnummer,DWORD detailfehler,
WORD modulnummer,WORD location,va_list args)
needs to be
void vSystemfehlerprotokollierenText(BYTE quelle,WORD fehlercode,WORD subfehlercode,
BYTE klassifizierung,BYTE status,BYTE kanalnummer,DWORD detailfehler,
WORD modulnummer,WORD location,char * form, va_list args)
the function SystemfehlerKGhas to be changed also to reflect the parameter form.
One thing still isnt very clear to me:
If I am using the ... in my top most function and want to call another function that uses the ... signature, what must i be aware of?
Your edit is well-spotted. In printf and friends, the format string is not part of the variadic part of the function's signature. You have to pass it separately as a normal argument.
The other important bit: you always have to pass on the va_list structure to vsnprintf_s (via your vSystemfehlerprotokollieren), even if you're sure there are no extra arguments. So, don't make the va_start and va_end conditional.
I'm hooking printf-like function that is defined like this:
int Con_Printf(const char *format, ...)
I want to check if a given string is within the arguments or in the final formatted string and if is there, then replace it with another string, but I have the following code to pass the arguments to the original function:
va_list args;
char *parg;
va_start(args, format);
vasprintf(&parg, format, args);
va_end(args);
(*conprint)("%s", parg);
free(parg);
being (*conprint) the original Con_Printf function.
How can I view the string with format applied (i.e arguments replaced into placeholders) to replace what I need and not just pass through all the arguments?
Example:
The program calls Con_Printf("%s %s", "foo", "bar"), I want to know in my Con_Printf hook if "bar" is within the arguments, but I don't know how many arguments are there because of the ", ...)" definition of Con_Printf.
If "bar" is found within the arguments, then replace it for "baz" and forward a modified call (*conprint)("%s %s", "foo", "baz") (replaced bar with baz). (*conprint)("foo baz") would do the trick too.
Thanks!
I think, what you want is va_arg(). Check the man page here. Instead of directly using vasprintf(), you may want to
take out one argument at a time
perform required checks and operations
then put them together for the final conprint.
from the manual page, the historic setup is:
#include <varargs.h>
void foo(va_alist)
va_dcl
{
va_list ap;
va_start(ap);
while (...) {
...
x = va_arg(ap, type);
...
}
va_end(ap);
}
You need to choose the type based on the supplied format specifier
Well, I think I missed the fact that the string part should contain the already formatted text, so I could directly do string manipulation on there to replace the needle.
So, my code finally ended like this:
va_list args;
char *parg;
va_start(args, format);
vasprintf(&parg, format, args);
va_end(args);
char* replacedstr;
replacedstr = str_replace(parg, "bar", "baz");
(*conprint)("%s", replacedstr);
free(parg);
Being str_replace a nice function that I've found somewhere (I've never coded in C, just doing experiments).
static (*__cdecl)F_HOOK *edit_f(&printf, sizeof(printf))
{
__cdecl _:printf
{
/* .. code before executing printf stuff.. */
}
}
my problem with vsprintf is that I can not obtain input arguments directly, I have to first get inputs one by one and save them in void**, then pass this void** to vsprintf(), it is all fine for windows, but when I come to 64bit linux, gcc cannot compile because it is not allowed to convert from void** to va_list, Is there anyone can give me some help how I should do under linux?
part of my code is:
void getInputArgs(char* str, char* format, ...)
{
va_list args;
va_start(args, format);
vsprintf(str, format, args);
va_end(args);
}
void process(void)
{
char s[256];
double tempValue;
char * tempString = NULL;
void ** args_ptr =NULL;
ArgFormatType format; //defined in the lib I used in the code
int numOfArgs = GetNumInputArgs(); // library func used in my code
if(numOfArgs>1)
{
args_ptr = (void**) malloc(sizeof(char)*(numOfArgs-1));
for(i=2; i<numOfArgs; i++)
{
format = GetArgType(); //library funcs
switch(format)
{
case ArgType_double:
CopyInDoubleArg(i, TRUE, &tempValue); //lib func
args_ptr[i-2] = (void*) (int)tempValue;
break;
case ArgType_char:
args_ptr[i-2]=NULL;
AllocInCharArg(i, TRUE, &tempString); //lib func
args_ptr[i-2]= tempString;
break;
}
}
}
getInputArgs(s, formatString, (va_list) args_ptr); /////Here is the location where gcc cannot compile
}
Many Many thanks!!
The problem is, your function gets ..., but you are passing it a va_list. ... is used for a usage like this:
getInputArgs(s, formatString, arg1, arg2, arg3, arg4 /* etc */);
and it won't work with va_list. Unfortunately, there is not an easy way to create a va_list from different parameters instead of getting it from .... See this question for example.
What you should do is to change the way you want to print to the string.
You can have:
char s[256];
int so_far = 0;
And in your for loop instead of something like this:
CopyInDoubleArg(i, TRUE, &tempValue); //lib func
args_ptr[i-2] = (void*) (int)tempValue;
You write:
CopyInDoubleArg(i, TRUE, &tempValue); //lib func
if (so_far < 256) /* 256 is the maximum length of s */
so_far += snprintf(s + so_far, 256 - so_far, "%lf", tempValue);
Something along these lines. This way, you create the string one by one, appending each element to the previous, instead of trying to make it all at once.
Assuming if (websValidateUrl(wp, path) < 0) is true in function below:
int websDefaultHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg,
char_t *url, char_t *path, char_t *query)
{
websStatType sbuf;
char_t *lpath, *tmp, *date;
int bytes, flags, nchars;
a_assert(websValid(wp));
a_assert(url && *url);
a_assert(path);
a_assert(query);
/*
* Validate the URL and ensure that ".."s don't give access to unwanted files
*/
flags = websGetRequestFlags(wp);
if (websValidateUrl(wp, path) < 0)
{
websError(wp, 500, T("Invalid URL %s"), url); //points to valid string "/index.html"
return 1;
}
}
url is then passed into here, where fmt is iterated with va_start():
void websError(webs_t wp, int code, char_t *fmt, ...)
{
va_list args;
char_t *msg, *userMsg, *buf;
char_t* safeUrl = NULL;
char_t* safeMsg = NULL;
#ifdef qRichErrorPage
static int reEntry = 0;
int errorOk;
#endif
a_assert(websValid(wp));
a_assert(fmt);
websStats.errors++;
/* remove any dangerous characters in the url, and replace the string in the
* wp structure. The webs_t cleanup code will free this memory for us.
*/
safeUrl = websSafeUrl(wp->url);
bfreeSafe(B_L, wp->url);
wp->url = safeUrl;
va_start(args, fmt); //AT this point args is a bad pointer??
userMsg = NULL;
fmtValloc(&userMsg, WEBS_BUFSIZE, fmt, args);
va_end(args);
.
.
.
By the time we get to va_start(args, fmt); args contains some weird characters, not the "/index.html" I was expecting.
This pointer was allocated at the level higher than the first function but shouldn't it still be there as it is valid in websDefaultHandler?
In general what is the best practice for doing something like this? Do I need to allocate memoery again for it in websDefaultHandler before passing it to websError()?
Any help is appreciated.
Once you initialize args with va_start, you should be using va_arg to retrieve the actual values.
(Already said in comments, but pulling out into an actual answer since it seems to be the issue here:)
If it happens that the url being passed into websError is the same string (meaning, the same piece of memory) as wp->url, then that call to bfreeSafe is freeing it before fmtValloc tries to use it, in which case it's no surprise if fmtValloc sees something garbled.