Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

SPRINTF


  • Please log in to reply
17 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
sprintf is accessible via dll calls from Msvcrt.dll. (Msvcrt.dll contains the C Run-Time Library for programs compiled with Visual C++, versions 4.2 to 6. In newer Windows versions (XP) this file is included as part of the operating system; otherwise download it from <!-- m -->http://www.dll-files... ... tml?msvcrt<!-- m --> )

It can also be handy to format your data to be displayed, or convert the data representation. Here is an example, with max 999 character output, and 4 (optional) parameters with their AHK types.
sprintf(format, f0="",v0="",f1="",v1="",f2="",v2="",f3="",v3="") { ; f = Double, Int64(->%I64..), Int, Str, Char
   VarSetCapacity(S,999)
   DllCall("msvcrt\sprintf", Str,S, Str,format, f0,v0, f1,v1, f2,v2, f3,v3)
   Return S
}
It can be used for converting numbers to hex integers
HEX(n) {
   VarSetCapacity(S,20)
   Return DllCall("msvcrt\sprintf", Str,S, Str,"0x%I64x", Int64,n) ? S : S
}
MsgBox % Hex(1025)
MsgBox % Hex(-1)
sprintf does not handle single precision float parameters. Accordingly, real number format types (e, f, g) process 64-bit (double) arguments, so always specify "Double" for the f0, f1… parameters (never "float").

Integer types (d, i, o, u, x) take 32-bit integer parameters, so for f0, f1… specify "Int" or "UInt". If you want 64-bit integer types, in the format parameter precede the types with I64 (I64d, I64i, I64o, I64u or I64x) specify "Int64" for the f0, f1… parameters.

String type (s) is also supported with f = "Str", like char © with f = "Char", and char counting (n). Here is a summary of the format placeholder: %[flags][width][.precision][length]type

flags can be omitted or be any of:

'+' : Causes sprintf to always denote the sign '+' or '-' of a number (the default is to omit the sign for positive numbers). Only applicable to numeric types.
'-' : Causes sprintf to left-align the output of this placeholder (the default is to right-align the output).
'#' : Alternate form. For 'g' and 'G', trailing zeros are not removed. For 'f', 'F', 'e', 'E', 'g', 'G', the output always contains a decimal point. For 'o', 'x', and 'X', a 0, 0x, and 0X, respectively, is prepended to non-zero numbers.
' ' : (space) Causes sprintf to left-pad the output with spaces until the required length of output is attained. If combined with '0' (see below), it will cause the sign to become a space when positive, but the remaining characters will be zero-padded
'0' : Causes sprintf use '0' (instead of spaces) to left fill a fixed length field. For example (assume i = 3) sprintf("%2d", i) results in " 3", while printf("%02d", i) results in "03"

Width can be omitted or be any of:

a number : Causes sprintf to pad the output of this placeholder with spaces until it is at least number characters wide. If number has a leading '0', then padding is done with '0' characters.
'*' : Causes sprintf to pad the output until it is n characters wide, where n is an integer value stored in the a function argument just preceding that represented by the modified type.

.Precision can be omitted or be any of:

a number : For non-integral numeric types, causes the decimal portion of the output to be expressed in at least number digits. For the string type, causes the output to be truncated at number characters.
'*' : Same as the above, but uses an integer value in the intaken argument to determine the number of decimal places or maximum string length. For example, printf("%.*s", 3, "abcdef") will result in "abc" being printed.
If the precision is zero, nothing is printed for the corresponding argument.

Length can be omitted or be:

'I64' : 64-bit integer type

type can be any of:

'd', 'i' : Print an int as a signed decimal number.
'u' : Print decimal unsigned int.
'f', 'F' : Print a double in normal (fixed-point) notation.
'e', 'E' : Print a double value in standard form ([-]d.ddd e[+/-]ddd).
'g', 'G' : Print a double in either normal or exponential notation, whichever is more appropriate for its magnitude. 'g' uses lower-case letters, 'G' uses upper-case letters. Insignificant zeroes to the right of the decimal point are not included and the decimal point is not included on whole numbers.
'x', 'X' : Print an unsigned int as a hexadecimal number. 'x' uses lower-case letters and 'X' uses upper-case.
'o' : Print an unsigned int in octal.
's' : Print a character string.
'c' : Print a char (character).
'p' : Print a pointer in 8 digit hex format with leading 0's.
'n' : Write number of characters successfully written so far into an integer pointer parameter.
'%' : Print a literal '%' character (this type doesn't accept any flags, width, precision or length).

You can test them with something like the following
VarSetCapacity(p,20)
MsgBox % sprintf("%p%n","Int",65, "Int",&p)
MsgBox % "Previously " . *&p | *(&p+1)<<8 . " chars were written"

MsgBox % sprintf("%s / %s = %c","Str","Numerator","Str","Denominator","Char",65)
MsgBox % sprintf("%12.9e, %f","Double",-400*atan(1),"Double",12345678901234567.0)
MsgBox % sprintf("%d, %d, %I64d","Int",-2,"Int",9876543210987654,"Int64",9876543210987654) ; v2 overflows


majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
ah, great discovery
Posted Image

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Great. As a matter of fact, I was thinking about replacing with the corresponding Windows' wsprintf/wnsprintf in your hex representation script for float/double, although my preferring way is using RtlMoveMemory.
Obviously, however, there was no simple way for double because of the little endian nature of Windows. But, this post encouraged me to try might-be undocumented or mistakenly omitted features. %e and %g didn't work, but %I64x did actually work with wsprintf too!

BTW, I was a bit surprised at that it worked in its current form. I think the calling convention definitely should be Cdecl here.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

the calling convention definitely should be Cdecl here.

Yes. You see it in the ErrorLevel, but I found no difference in the results with or without CDECL. We need some more experiments to learn all the angles. This was the reason behind my post.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Yes. You see it in the ErrorLevel, but I found no difference in the results with or without CDECL. We need some more experiments to learn all the angles. This was the reason behind my post.

Windows has some sort of safeguard to protect it from them. See the message from one of the API developers.
I think I once referred it already in the forum, at that time against the abuse of rundll32.
<!-- m -->http://blogs.msdn.co... ... 58973.aspx<!-- m -->

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

Windows has some sort of safeguard to protect...

If I understood right, the opposite causes the problem (declaring dll functions as cdecl instead of stdcall). Please keep on testing, and report any problems.

In the mean time I added more information to the first post, and test cases. This sprintf can turn to a very useful tool, giving full control of the message formats of AHK scripts.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

If I understood right, the opposite causes the problem (declaring dll functions as cdecl instead of stdcall). Please keep on testing, and report any problems.

It appears so because most Windows' APIs uses stdcall, thus most examples naturally come from it. Actually, the effect is the same with this case. The examples there are from the point of view of callee, here from the point view of caller, ie AHK's DllCall(). So, neither the caller nor the callee would try to clean up the stack in both cases.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The question is, how can we test for possible problems. I ran a loop 10 million times calling the above sprintf function. There was no memory leak, the system did not get instable. So what can go wrong? Can you suggest something else to try?

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
It's not on whether the code merely works or not. Merely working doesn't mean the code is actually bug-free. It's working because Window's developers always spent substantial portion of the developer time to support the buggy codes too, to a surprise of many people.

I kinda expected it to work, so I said only 'a bit' surprised. I suppose being able to detect An error already implied the protection. Although I also deliberately took an advantage of some unexpected behaviors of the providers sometimes, I would never rely on their support for buggy codes.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I seem to remember that DllCall itself repairs the stack when too many or too few args were passed (or CDecl was accidentally omitted). A careful review of the code would be required to see if it's truly safe to always omit CDecl.

Also, CDecl might perform better than the standard calling convention; but even if so, the extra time required to parse the word "CDecl" might offset the savings.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

It's working because Window's developers always spent substantial portion of the developer time to support the buggy codes

It works, because dllcalls have to-, and do clean up the stack (as experiments show). In case of variable number of arguments, like with sprintf, only dllcalls know what they have written to the stack. Also, if the number of bytes stacked is wrong (bug), the called function is not able to clean up the stack, and if AHK did not do it, a simple bug would crash the PC. Accordingly, omitting CDECL is no bug, but relying on the proper behavior of AHK.

Running time of 20 million sprintf calls in my DELL Inspiron 9300 (2GHz Centrino), XP SP2:
No CDECL --> 59.093s
"CDECL" --> 63.250s
"CDECL INT" --> 64.562s

These are why I posted the functions w/o CDECL. Of course, anyone can add it to his own version, we just try to provide the information to help him with his decision.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

I seem to remember that DllCall itself repairs the stack when too many or too few args were passed (or CDecl was accidentally omitted). A careful review of the code would be required to see if it's truly safe to always omit CDecl.

Ah, so it's AHK, not Windows, to protect the stack corruption. Very considerate design! No wonder why DllCall() is so stable. As far as I remember, it crashed AHK only when specified an invalid memory address.

BTW, strangely, the case where Cdecl was essential was with fastcall. I have no idea why/how Cdecl makes fastcall to work, and whether it's safe to use, even when the size of the parameters exceeding 8 bytes.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

It works, because dllcalls have to-, and do clean up the stack (as experiments show). In case of variable number of arguments, like with sprintf, only dllcalls know what they have written to the stack. Also, if the number of bytes stacked is wrong (bug), the called function is not able to clean up the stack, and if AHK did not do it, a simple bug would crash the PC. Accordingly, omitting CDECL is no bug, but relying on the proper behavior of AHK.

Why didn't you tell it before Chris posted?
Why do you think then the mentioned developer cares so much about the correct calling convention?
What is the purpose of the various types of the calling convention anyway?

You must thank Chris to protect your code from crashing AHK and your PC. Enough said.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

Why didn't you tell [why it works] before Chris posted?

I did tell that there were no problems after long test sessions. Many of us already saw that the wrong number of parameters passed to dllcalls does not crash AHK, and there were no memory leaks. You can conclude that AHK cleans up the stack. Who else could? No one knows what was pushed on the stack. Also, in the help there are cases listed, where dll calls crash the script. All of them are related to wrong addresses, not to the number of bytes passed to the function, implicitly hinting that the later is not catastrophic.

What is the purpose of the various types of the calling convention anyway?

One example is that Errorlevel is a good debug tool about the right number of parameters for standard calls. In case of CDECL, we don't get it. Here is an example:
x := DllCall("msvcrt\sin", "Double", 1.5, "CDECL Double")
MsgBox [%x%]`n[%ErrorLevel%]

x := DllCall("msvcrt\sin", "Double", 1.5, "Double", 1.5, "CDECL Double")
MsgBox [%x%]`n[%ErrorLevel%]

x := DllCall("msvcrt\sin", "Double", 1.5, "Double")
MsgBox [%x%]`n[%ErrorLevel%]
The second call has the wrong number of parameters, but ErrorLevel = 0, and the result is correct. The third call is not C-type, still works just fine. Only ErrorLevel = A8, because 8 bytes are pushed on the stack, and AHK believes 0 bytes should have been pushed. This behavior is known and exploited, and no reports on crashes have been posted. These taught us long ago, that AHK cleans up the stack gracefully.

You must thank Chris to protect your code from crashing AHK and your PC.

Of course. dllcalls are implemented the right way. Variable number of arguments cannot be handled otherwise. Still, you have a good point: there is very little specified about the behavior of dllcalls, so whatever we do, it relies partially on undocumented features, which is not nice, but unavoidable.

I am not a programmer, so I cannot do it in reasonable time, but I would appreciate if some experts dig out the details and wrote some detailed documentations about dllcalls.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Variable number of arguments cannot be handled otherwise.

You still seem to not get what Cdecl is supposed to do. Anyway, this is not a critic.
I just want to apologize to you as I think I overreacted.