Among our members are many very experienced C programmers who would be highly familiar with the basics of the standard C runtime library that the MSVCRT.DLL covers. I had an idea in mind where there is such a body of expertise available that some out of their own code or stuff they have seen on a regular basis would have some ideas on leveraging this capacity so that more of the cde would be available to programmers writing code in MASM.
Just as an example the demo that Greg wrote also had an algo at the end of it that could easily be tweaked into a "pause" macro for console type applications and there will of course be many other things that can readily be derived from such a large runtime library.
I thought it was worth the effort to float this past the guys who have many years of C experience to see if there were some useful bits and pieces that could be made into macros for MASM.
PS : One concession, PLEASE put your name on any pieces of genius so we all know who wrote it.
Here is the first piece of genius. :dazzled:
Its a console macro to emulate the dos "pause".
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
pause MACRO user_text:VARARG
;; -------------------------------------
;; display user defined text or default
;; text if there is no user defined text
;; -------------------------------------
IFNB <user_text>
print user_text ;; user defined text
ELSE
print "Press any key to continue ..." ;; default text
ENDIF
invoke crt__getch ;; wait for key
;; ----------------------------------------------
;; place cursor on next line after key is pressed
;; ----------------------------------------------
print chr$(13,10)
ENDM
.data
tst db "This is a test",0
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
pause
pause "Pause with user defined text message"
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
LATER :
Slight change to the macro.
LATER AGAIN :
Here are two more.
env$ MACRO item
fn crt_getenv,item
EXITM <eax>
ENDM
setenv MACRO value ; set environment in current thread
fn crt__putenv,value
ENDM
They are used as follows.
setenv "link=\masm32\bin\"
mov penv, env$("link")
.if penv != 0
print penv,13,10
.endif
Here are three macros and a test app. For some reason, even though printf outputs to STDOUT, the output cannot be redirected to a file. For the printfr macro it would have been better to use _scprintf instead of a fixed size buffer, but the function apparently is not included in MSVCRT.DLL. A single call to _getch will not pause under all conditions. If the user presses an extended key two (or more?) characters will be placed in the buffer, and the next call will not wait. I added code to flush the buffer a second time just to ensure it would be left empty.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; ---------------------------------------
; The output for this version cannot be
; redirected to a file.
; ---------------------------------------
printf MACRO format:REQ,args:VARARG
IFNB <args>
invoke crt_printf,reparg(format),args
ELSE
invoke crt_printf,reparg(format)
ENDIF
ENDM
; ------------------------------------
; The output for this version can be
; redirected to a file.
; ------------------------------------
printfr MACRO format:REQ,args:VARARG
push ebx
mov ebx,alloc$(256)
IFNB <args>
invoke crt__snprintf,ebx,256,reparg(format),args
ELSE
invoke crt__snprintf,ebx,256,reparg(format)
ENDIF
print ebx
free$(ebx)
pop ebx
ENDM
; -------------------------------------------------
; Flushes the input buffer, waits for a keystroke,
; and flushes the buffer again, leaving it empty.
; -------------------------------------------------
waitkey MACRO
@@:
call crt__kbhit
or eax, eax
jz @F
call crt__getch
jmp @B
@@:
call crt__getch
call crt__kbhit
or eax, eax
jnz @B
ENDM
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.data
dbl1 REAL8 1234.56789
dd1 dd 12345678h
str1 db "yada yada",10,0
.code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
printf ADDR str1
printf """quoted string""%c", 10
printf "((<<&>>))'""%%%c", 10
printf "break this %cline%c", 10, 10
printf "%xh = %dd%s", dd1, dd1, chr$(10)
printf "%f%c", dbl1, 10
printf "%0.2f%c", dbl1, 10
printf "%0.0f%c", dbl1, 10
printf "%010.2f%c%c", dbl1, 10, 10
waitkey
printfr ADDR str1
printfr """quoted string""%c", 10
printfr "((<<&>>))'""%%%c", 10
printfr "break this %cline%c", 10, 10
printfr "%xh = %dd%s", dd1, dd1, chr$(10)
printfr "%f%c", dbl1, 10
printfr "%0.2f%c", dbl1, 10
printfr "%0.0f%c", dbl1, 10
printfr "%010.2f%c", dbl1, 10
waitkey
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Michael,
Thanks for the waitkey macro, I had tested the first one on single characters and and in conjunction with 1 key like ALT or CTRL but not in pairs. This one does the job fine. I just added your macro into the "pause" to get the user defined text or default text and it looks like this.
LATER : Tweaked to handle "pause NULL"
pause MACRO user_text:VARARG
IFDIF <user_text>,<NULL> ;; if user text not "NULL"
IFNB <user_text>
print user_text ;; user defined text
ELSE
print "Press any key to continue ..." ;; default text
ENDIF
ENDIF
;; -------------------------------------
;; display user defined text or default
;; text if there is no user defined text
;; -------------------------------------
@@:
call crt__kbhit
or eax, eax
jz @F
call crt__getch
jmp @B
@@:
call crt__getch
call crt__kbhit
or eax, eax
jnz @B
;; ----------------------------------------------
;; place cursor on next line after key is pressed
;; ----------------------------------------------
print chr$(13,10)
ENDM
I am not good at writing macros but I would write it like this:
pause MACRO user_text:VARARG
IFDIF <user_text>,<NULL> ;; if user text not "NULL"
IFNB <user_text>
print chr$(13,10)
print user_text ;; user defined text
ELSE
print chr$(13,10)
print "Press any key to continue ..." ;; default text
ENDIF
ENDIF
;; -------------------------------------
;; display user defined text or default
;; text if there is no user defined text
;; -------------------------------------
call crt__getch ; regular keys return just a key code
.if eax == 0 || eax == 0E0h ; extended keys return 0 plus a key code
; numeric pad keys return 0E0h plus a key code
call crt__getch ; another call is needed for the key code
.endif
;; ----------------------------------------------
;; place cursor on next line after key is pressed
;; ----------------------------------------------
print chr$(13,10)
ENDM
Regular keys return just a key code. Extended keys return 0 plus a key code. Extended keys on the numeric pad return 0E0h plus a key code.
_getch reference on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__getch.2c_._getche.asp)
Note: I am using ML.EXE v6.15 and it doesn't like 'pause'. I can't find a reference to that instruction. If I change it to 'waitkey' no problem.
pause.asm(58) : error A2085: instruction or register not accepted in current CPU mode
Greg,
I was trying to ensure that the buffer would always be emptied, so later inputs (of any sort) could not be affected. On my Windows 2000 system, with a US keyboard, _getch seems to always return no more than two characters, but can the function be depended on to behave the same for all systems/keyboards?
This is a test app to check the number of characters returned, and to perform a test of both methods to determine how reliably they will empty the buffer. Neither method is completely reliable under heavy system loads.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.data
.code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
L1:
call crt__kbhit
or eax, eax
jz @F
call crt__getch
jmp L1
@@:
call crt__getch
.if eax == 27
print chr$(13,10)
jmp @F
.endif
push eax
print uhex$(eax),32
pop eax
call crt__kbhit
or eax, eax
jnz @B
print chr$(13,10)
call crt__kbhit
or eax, eax
jz L1
call crt__getch
print uhex$(eax)
print chr$("<-- left in buffer"),13,10
jmp L1
@@:
call crt__getch
.if eax == 27
exit
.endif
push eax
print uhex$(eax),32
pop eax
.if eax == 0 || eax == 0E0h
call crt__getch
push eax
print uhex$(eax)
pop eax
.endif
print chr$(13,10)
call crt__kbhit
or eax, eax
jz @B
call crt__getch
print uhex$(eax)
print chr$("<-- left in buffer"),13,10
jmp @B
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Damn,
I get the same problem with the name "pause" on ML 7.00 as well. I was trying to retain the DOS name so it would be familiar. Thanks for the MSDN link Greg, the code you posted makes sense in that context.
I am interested in Michael's comment of what constitutes a heavy load as far as a keyboard buffer goes that would effect the reliability of the function.
MichaelW,
I would say yes, _getch will always return no more than two characters.
I ran your program, either method works equally well, and yours does flush the keyboard buffer before waiting for a keystroke. I was thinking of using fllush(stdin) to clear the keyboard buffer before waiting for a keystroke. C purists will howl at fflush(stdin) as it is not standard C, but MSVC (and Pelle's C) supports it, so I have no problem using it. I imagine it just calls FlushConsoleInputBuffer but I'm not sure.
Your method is probably faster than using fflush(stdin).
I just wanted to point out the need to test for 0E0h, but with your method you don't need to test for it. Again, I didn't thoroughly examine your code before opening my mouth. :bg
Here is another try, it looks like it works OK but is no improvement on either of the ones posted. Two API based macros as well.
wait_key MACRO
LOCAL label
label:
fn Sleep,10
call crt__kbhit
test eax, eax
jz label
print chr$(13,10)
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
ENDM
date$ MACRO
IFNDEF @_@_current_local_date_@_@
.data?
@_@_current_local_date_@_@ db 128 dup (?)
.code
ENDIF
invoke GetDateFormat,LOCALE_USER_DEFAULT,DATE_LONGDATE,
NULL,NULL,ADDR @_@_current_local_date_@_@,128
EXITM <OFFSET @_@_current_local_date_@_@>
ENDM
time$ MACRO
IFNDEF @_@_current_local_time_@_@
.data?
@_@_current_local_time_@_@ db 128 dup (?)
.code
ENDIF
invoke GetTimeFormat,LOCALE_USER_DEFAULT,NULL,NULL,NULL,
ADDR @_@_current_local_time_@_@,128
EXITM <OFFSET @_@_current_local_time_@_@>
ENDM
Hutch,
To get a heavy load on my system, all I have to do is start the programming environment for any of the Microsoft Quick languages, including QBasic. When I do so the CPU time for the System Idle Process drops to 0%, a ntvdm.exe task appears with a CPU time of 98-99%, and everything visibly slows down. If I then run the app from my last post and type on the keyboard at a moderate rate, at random intervals one or more keystrokes will be left in the buffer. This is with a P3-500; a faster processor may reduce or eliminate the problem.
Michael,
Thanks, I think I understand what is happening there.
Here is the current test piece, with "pause" being a reserve word in later MASM versions I opted for the traditional basic "inkey" and moved the code for the operation into a procedure as there is no point inlining it as it is hardly speed critical.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
date$ MACRO
IFNDEF @_@_current_local_date_@_@
.data?
@_@_current_local_date_@_@ db 128 dup (?)
.code
ENDIF
invoke GetDateFormat,LOCALE_USER_DEFAULT,DATE_LONGDATE,
NULL,NULL,ADDR @_@_current_local_date_@_@,128
EXITM <OFFSET @_@_current_local_date_@_@>
ENDM
time$ MACRO
IFNDEF @_@_current_local_time_@_@
.data?
@_@_current_local_time_@_@ db 128 dup (?)
.code
ENDIF
invoke GetTimeFormat,LOCALE_USER_DEFAULT,NULL,NULL,NULL,
ADDR @_@_current_local_time_@_@,128
EXITM <OFFSET @_@_current_local_time_@_@>
ENDM
;; ----------------------------------
;; display user defined text, default
;; text or none if NULL is specified.
;; ----------------------------------
inkey MACRO user_text:VARARG
LOCAL label
IFDIF <user_text>,<NULL> ;; if user text not "NULL"
IFNB <user_text>
print user_text ;; print user defined text
ELSE
print "Press any key to continue ..." ;; print default text
ENDIF
ENDIF
call wait_key
print chr$(13,10)
ENDM
env$ MACRO item
fn crt_getenv,item
EXITM <eax>
ENDM
setenv MACRO value
fn crt__putenv,value
ENDM
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
LOCAL penv :DWORD
fn SetConsoleTitle,"Ain't MASM beautiful ?"
print "Today's date is "
print date$(),13,10,13,10
print "You started this program at "
print time$(),13,10
setenv "link=\masm32\bin\"
mov penv, env$("link")
.if penv != 0
print penv,13,10
.endif
inkey NULL
inkey
inkey "Pause with user defined text message"
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
wait_key proc
@@:
invoke Sleep,10
call crt__kbhit
test eax, eax
jz @B
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
ret
wait_key endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
hutch--,
What is it with you and idle loops? :green
:bg
Old habits die hard but on the bright side, I may add a keystroke escape to a shell proc using the technique.
LATER :
This is the Microsoft code from thier MSDN site on how to use the function, I seriously think mine is better.
// crt_kbhit.c
// compile with: /c
/* This program loops until the user
* presses a key. If _kbhit returns nonzero, a
* keystroke is waiting in the buffer. The program
* can call _getch or _getche to get the keystroke.
*/
#include <conio.h>
#include <stdio.h>
int main( void )
{
/* Display message until key is pressed. */
while( !_kbhit() )
_cputs( "Hit me!! " );
/* Use _getch to throw key away. */
printf( "\nKey struck was '%c'\n", _getch() );
}
That doesn't look much like an example to follow :bg.
Here is a function I've used a couple of times -- it uses WaitForSingObject to do an efficient wait, as mentioned on the MSDN page about Low-Level Console Input Functions (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/low_level_console_input_functions.asp):
void waitforkeypress()
{
HANDLE hin;
DWORD dwNumEvents, dwNumRead;
INPUT_RECORD ir;
/* get standard input handle */
hin = GetStdHandle(STD_INPUT_HANDLE);
if (hin == INVALID_HANDLE_VALUE) return;
/* flush input buffer */
FlushConsoleInputBuffer(hin);
/* wait loop */
while (1)
{
/* wait for an input event */
WaitForSingleObject(hin, INFINITE);
/* get number of events */
if (GetNumberOfConsoleInputEvents(hin, &dwNumEvents) && dwNumEvents)
{
/* loop through events */
while (dwNumEvents--)
{
/* read event */
if (ReadConsoleInput(hin, &ir, 1, &dwNumRead) && dwNumRead)
{
/* if it's a key being released, return */
if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown == 0) return;
}
}
}
}
}
Jibz,
It certainly looks a lot better than the MSDN version. There is a tool have taken a liking to from sysinternals, its called Process Explorer and it tells you some interesting stuff, system idle process sits there taking a bit over 90%, the balance is interrupts and the sum total of the rest are too small to measure so they show 0%. Start up the polling loop as per above and the numbers remain the same, it sits there not consuming enough processor time to register above 0%.
Run something processor intensive that is continuously taking processor time and it takes it from the system idle process.
This is why I have no problems with idle loops as they perform well.
Just as a later thought, I am yet to understand the prejudice against polling loops, they are like anything else, write them properly and they work fine, do it wrong and they will almost lock a box up. Over time I have heard many fashions in programming, you should not use GOTO in a program but should use structured loops. You should not use GLOBAL scope variables but always use LOCALS passed on the stack.
The problem is there is a whole mountain of code that you cannot write if you have restrictions on such techniques. Now in response the the GOTO boogie, I instead only use JUMPS and as few as I can get away with and I simply ignore the "rule" on LOCALS only if I need the scope of a GLOBAL so I wonder if there is a new rule of fashion on using polling loops ?
The basic mechanics is there is no other way of performing regular interval checks on events that test for a result. You can pass this off to the operating system if you like but it still must do the same to determine the results of events. On the win2k and later OS versions this is the 7 to 8% at idle processor usage of interrupts so I don't see that it is all that efficient in terms of processor usage. While I see that Windows is poorly designed in terms of low level access, this type of loop is trivial to write from ring3 access and they test up fine if they are written properly.
This is the latest version of the test piece, added title bar text control for a console using standard API functions. The wait_key procedure needs to be added to the masm32 library.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
date$ MACRO
IFNDEF @_@_current_local_date_@_@
.data?
@_@_current_local_date_@_@ db 128 dup (?)
.code
ENDIF
invoke GetDateFormat,LOCALE_USER_DEFAULT,DATE_LONGDATE,
NULL,NULL,ADDR @_@_current_local_date_@_@,128
EXITM <OFFSET @_@_current_local_date_@_@>
ENDM
time$ MACRO
IFNDEF @_@_current_local_time_@_@
.data?
@_@_current_local_time_@_@ db 128 dup (?)
.code
ENDIF
invoke GetTimeFormat,LOCALE_USER_DEFAULT,NULL,NULL,NULL,
ADDR @_@_current_local_time_@_@,128
EXITM <OFFSET @_@_current_local_time_@_@>
ENDM
;; ----------------------------------
;; display user defined text, default
;; text or none if NULL is specified.
;; ----------------------------------
inkey MACRO user_text:VARARG
IFDIF <user_text>,<NULL> ;; if user text not "NULL"
IFNB <user_text>
print user_text ;; print user defined text
ELSE
print "Press any key to continue ..." ;; print default text
ENDIF
ENDIF
call wait_key
print chr$(13,10)
ENDM
SetConsoleCaption MACRO title_text
invoke SetConsoleTitle,reparg(title_text)
ENDM
GetConsoleCaption$ MACRO
IFNDEF @@_console_caption_buffer_@@
.data?
@@_console_caption_buffer_@@ db 260 dup (?)
.code
ENDIF
invoke GetConsoleTitle,ADDR @@_console_caption_buffer_@@,260
EXITM <OFFSET @@_console_caption_buffer_@@>
ENDM
env$ MACRO item
fn crt_getenv,item
EXITM <eax>
ENDM
setenv MACRO value
fn crt__putenv,value
ENDM
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
LOCAL penv :DWORD
SetConsoleCaption "Ain't MASM beautiful ?"
print "Today's date is "
print date$(),13,10,13,10
print "You started this program at "
print time$(),13,10,13,10
print "The console caption is '"
print GetConsoleCaption$(),"'",13,10
setenv "link=\masm32\bin\"
mov penv, env$("link")
.if penv != 0
print penv,13,10
.endif
inkey NULL
inkey
inkey "Wait for keyboard input with user defined text ",62," "
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
wait_key proc
@@:
invoke Sleep,10
call crt__kbhit
test eax, eax
jz @B
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
ret
wait_key endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Hutch,
I tried out the latest macros, they look good to me. I watched the CPU Usage when 'wait_key' is looping and it is essentially zero.
Greg,
Thansk for giving it a blast, I don't think it works any better than either yours or Michaels but its smaller and simpler. It makes more sense to put it in a library module than inline it as its hardly speed critical and just let the macro handle the string options. I think your suggestion to use FlushConsoleInputBuffer() was a good idea as it did simplify the code.
Here is a freestyle variation of the wait_key proc, it automatically counts down from 10 seconds to zero and exits if a key has not been pressed while updating the console window title. It could probably do with some tidying up but it seems to work OK.
LATER : Tidied up a bit.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
wait_key proc
LOCAL tc :DWORD
LOCAL cnt :DWORD
LOCAL buf :DWORD
LOCAL buffer[32]:BYTE
mov tc, rv(GetTickCount)
add tc, 1000
mov cnt, 10
jmp ifin
@@:
invoke Sleep,10
.if rv(GetTickCount) >= tc
add tc, 1000
sub cnt, 1
jz @F
ifin:
mov buf, ptr$(buffer)
SetConsoleCaption cat$(buf,str$(cnt)," seconds left")
.endif
test rv(crt__kbhit), eax
jz @B
@@:
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
ret
wait_key endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
Are we adding all these tasty little tidbidts to SP3? :bg
Here's my suggestion for a pause/wait_key function. It doesn't use the function from the C runtime (which I had to avoid because I couldn't find an include/library for it :eek) but I think it's just as neat:
wait_key PROC
LOCAL hConsole:DWORD
LOCAL dwConsoleMode:DWORD
invoke GetStdHandle, STD_INPUT_HANDLE
mov hConsole, eax
invoke GetConsoleMode, hConsole, ADDR dwConsoleMode
invoke SetConsoleMode, hConsole, 0
invoke ReadFile, hConsole, NULL, 1, NULL, NULL
invoke SetConsoleMode, hConsole, dwConsoleMode
invoke FlushConsoleInputBuffer, hConsole
ret
wait_key ENDP
The local variables could probably be avoided by using push/pops or registers, but either way its going to use the stack so I decided to go with the easiest to read. And as hutch (I think) mentioned earlier, it's hardly time critical code and when you play with APIs optimization goes out the MS Windows anyway :bg :cheekygreen:
zooba,
Thanks for this piece of genius, it works fine, handles the combined keys correctly and has very low processor usage. :U
None of the 'special' keys (like CTRL, ALT, SHIFT, ESC, arrow keys, F-keys, etc.) seem to work with that version?
Edit: I just noticed that CTRL, ALT and SHIFT did not seem to work with the polling loop version either.
Edit 2: Of course those three don't work with the 'pause' command either, so it's not a big problem unless you're the type who takes "Press any key to continue..." very literally :toothy.
Quote from: Greg on June 10, 2005, 07:27:10 PM
Note: I am using ML.EXE v6.15 and it doesn't like 'pause'. I can't find a reference to that instruction. If I change it to 'waitkey' no problem.
pause.asm(58) : error A2085: instruction or register not accepted in current CPU mode
PAUSE is new since Pentium4, but is backward compatible with all IA-32 processors, because its opcode corresponds to REP NOP instruction.
zooba,
Nice code :U, that works better than the Win32 API version of "waitkey" I had.
MazeGen,
Thanks for the info, I never found anything on the PAUSE instruction and gave up.
:bg
> Edit: I just noticed that CTRL, ALT and SHIFT did not seem to work with the polling loop version either.
But look on the bright side, it would be easy enough to add URL checking, DRM testing and with just a bit more work, multimedia support. Imagine having music while you made up your mind which key to press. We could usher in a new era of user interaction at the console level that may see the BIG return to true console apps. :toothy
Please excuse Hutch, he's been 'testing' his homebrew again :lol
Nah,
My code is unlicenced as GPA, Glenfiddich Powered Assembler.
Hutch,
I see a potential problem with your latest waitkey procedure. If the user presses a key at some point before waitkey is called, it will not wait. While some users might expect a key pressed ahead of time to have an effect, I think most users would not.
Yes,
Its a good point, if it tests up as a problem, it would require the FlushConsoleInputBuffer() API call to be made both before and after to be safe.
LATER :
I changed the test piece with a Sleep,3000 and pressed a key a number of times and it allowed the first following wait_key procedure to fall through so adding the extra API call seems to fix that potential problem fine. This is the proc with the added API call.
wait_key proc
LOCAL hConsole :DWORD
mov hConsole, rv(GetStdHandle,STD_INPUT_HANDLE)
invoke FlushConsoleInputBuffer,hConsole
@@:
invoke Sleep,10
call crt__kbhit
test eax, eax
jz @B
invoke FlushConsoleInputBuffer,hConsole
ret
wait_key ENDP
Damn, all this incredible bloat. :bg
I have been playing with the idea of a proc that returns the scancode of the key pressed but I cannot improve on a slight modification of Greg's version. This is what it looks like. About the only complaint is it returns garbage with the F keys.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
LOCAL var :DWORD
LOCAL pinp :DWORD ; pointer for input buffer
LOCAL inbuffer[8]:BYTE ; input buffer
mov pinp, ptr$(inbuffer)
cls
print "Press a key to display its ASCII value or ESC to quit",13,10
backin:
call ret_key
mov ecx, pinp
mov [ecx], al ; write AL to 1st BYTE of buffer
.if al != 27 ; if not ESC
push eax
print pinp," = "
pop eax
print str$(eax),13,10
jmp backin
.endif
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
ret_key proc
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
call crt__getch
test eax, eax
jz @F
cmp eax, 0E0h
jnz quit
@@:
call crt__getch
quit:
ret
ret_key endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Hutch,
It seems to be working correctly on my system.
F1: ; = 59
F2: < = 60
F3: = = 61
F4: > = 62
F5: ? = 63
F6: @ = 64
F7: A = 65
F8: B = 66
F9: C = 67
F10: D = 68
F11: à = 133
F12: å = 134
Later: I should say it's returning the correct scan codes. My F11 and F12 codes are strange though.
Hutch,
I took your code and played with it a bit.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include c:\masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
LOCAL acode :DWORD ; ASCII code
LOCAL scode :DWORD ; Scan code
LOCAL vcode :DWORD ; Virtual-key code
LOCAL outbuffer[64]:BYTE
cls
print "Press a key (ESC to quit): ", 13, 10
backin:
call ret_key
.if al != 27 ; if not ESC
.if edx == 0
mov acode, eax
print SADD("ASCII code = ")
print hex$(acode)
print SADD("h, ASCII character = ")
mov eax, acode
mov outbuffer[0], al
mov outbuffer[1], 0
print ADDR outbuffer, 13, 10
.else
mov scode, eax
print SADD("Scan code = ")
print hex$(scode)
print chr$("h, Virtual-Key code = ")
invoke MapVirtualKey, scode, 1
mov vcode, eax
print hex$(vcode)
print chr$("h, Key name = ")
shl scode, 16
invoke GetKeyNameText, scode, ADDR outbuffer, SIZEOF outbuffer
print ADDR outbuffer, 13, 10
.endif
jmp backin
.endif
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
ret_key proc
; returns code in EAX
; EDX is 0 if ASCII, 1 if Scan Code
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
call crt__getch ; it's an ASCII Code
mov edx, 0
.if (eax == 0) || (eax == 0E0h)
call crt__getch ; it's a Scan Code
mov edx, 1
.endif
ret
ret_key endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Thanks Greg, using a register to flag an extended key is a good idea. This is the modded version.
ret_key proc
invoke FlushConsoleInputBuffer,rv(GetStdHandle,STD_INPUT_HANDLE)
call crt__getch
xor ecx, ecx
test eax, eax
jz @F
cmp eax, 0E0h
jnz quit
@@:
call crt__getch
mov ecx, 1
quit:
ret
ret_key endp
Zooba,
Quote from: GregNice code :U, that works better than the Win32 API version of "waitkey" I had.
Well, it's not perfect, your 'wait_key' procedure doesn't accept extended (non-ASCII) keys. I know, I'm being picky. :bg
Some 'number to string' conversion procedures that use the sprintf CRT function.
; Greg Lyon · 2005
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib msvcrt.lib
ByToStr PROTO byValue :BYTE, lpBuffer:PTR BYTE
SbyToStr PROTO sbyValue :SBYTE, lpBuffer:PTR BYTE
WdToStr PROTO wdValue :WORD, lpBuffer:PTR BYTE
SwdToStr PROTO swdValue :SWORD, lpBuffer:PTR BYTE
DwToStr PROTO dwValue :DWORD, lpBuffer:PTR BYTE
SdwToStr PROTO sdwValue :SDWORD, lpBuffer:PTR BYTE
QwToStr PROTO qwValue :QWORD, lpBuffer:PTR BYTE
SqwToStr PROTO sqwValue :QWORD, lpBuffer:PTR BYTE
FltToStr PROTO fltValue :REAL4, lpBuffer:PTR BYTE
DblToStr PROTO dblValue :REAL8, lpBuffer:PTR BYTE
LdblToStr PROTO ldblValue:REAL10, lpBuffer:PTR BYTE
WaitKey PROTO
.CONST
byTest BYTE 0FFh
sbyTest SBYTE 0FFh
wdTest WORD 0FFFFh
swdTest SWORD 0FFFFh
dwTest DWORD 0FFFFFFFFh
sdwTest SDWORD 0FFFFFFFFh
qwTest QWORD 0FFFFFFFFFFFFFFFFh
sqwTest QWORD 0FFFFFFFFFFFFFFFFh
fltTest REAL4 123.4567890
dblTest REAL8 456.7890123
ldblTest REAL10 789.0123456
.DATA
szBuffer BYTE 96 dup(0)
pszBuffer PBYTE szBuffer
.CODE
start:
invoke ByToStr, byTest, pszBuffer
invoke crt_puts, pszBuffer
invoke SbyToStr, sbyTest, pszBuffer
invoke crt_puts, pszBuffer
invoke WdToStr, wdTest, pszBuffer
invoke crt_puts, pszBuffer
invoke SwdToStr, swdTest, pszBuffer
invoke crt_puts, pszBuffer
invoke DwToStr, dwTest, pszBuffer
invoke crt_puts, pszBuffer
invoke SdwToStr, sdwTest, pszBuffer
invoke crt_puts, pszBuffer
invoke QwToStr, qwTest, pszBuffer
invoke crt_puts, pszBuffer
invoke SqwToStr, sqwTest, pszBuffer
invoke crt_puts, pszBuffer
invoke FltToStr, fltTest, pszBuffer
invoke crt_puts, pszBuffer
invoke DblToStr, dblTest, pszBuffer
invoke crt_puts, pszBuffer
invoke LdblToStr, ldblTest, pszBuffer
invoke crt_puts, pszBuffer
call WaitKey
invoke ExitProcess, 0
;======================================================
ByToStr PROC byValue:BYTE, lpBuffer:PTR BYTE
; byValue is considered unsigned
invoke crt_sprintf, lpBuffer, SADD("%u"), byValue
ret
ByToStr ENDP
;======================================================
SbyToStr PROC sbyValue:SBYTE, lpBuffer:PTR BYTE
; sbyValue is considered signed
invoke crt_sprintf, lpBuffer, SADD("%d"), sbyValue
ret
SbyToStr ENDP
;======================================================
WdToStr PROC wdValue:WORD, lpBuffer:PTR BYTE
; wdValue is considered unsigned
invoke crt_sprintf, lpBuffer, SADD("%hu"), wdValue
ret
WdToStr ENDP
;======================================================
SwdToStr PROC swdValue:SWORD, lpBuffer:PTR BYTE
; swdValue is considered signed
invoke crt_sprintf, lpBuffer, SADD("%hd"), swdValue
ret
SwdToStr ENDP
;======================================================
DwToStr PROC dwValue:DWORD, lpBuffer:PTR BYTE
; dwValue is considered unsigned
invoke crt_sprintf, lpBuffer, SADD("%lu"), dwValue
ret
DwToStr ENDP
;======================================================
SdwToStr PROC sdwValue:SDWORD, lpBuffer:PTR BYTE
; sdwValue is considered signed
invoke crt_sprintf, lpBuffer, SADD("%ld"), sdwValue
ret
SdwToStr ENDP
;======================================================
QwToStr PROC qwValue:QWORD, lpBuffer:PTR BYTE
; qwValue is considered unsigned
invoke crt_sprintf, lpBuffer, SADD("%I64u"), qwValue
ret
QwToStr ENDP
;======================================================
SqwToStr PROC sqwValue:QWORD, lpBuffer:PTR BYTE
; sqwValue is considered signed
invoke crt_sprintf, lpBuffer, SADD("%I64d"), sqwValue
ret
SqwToStr ENDP
;======================================================
FltToStr PROC fltValue:REAL4, lpBuffer:PTR BYTE
; REAL4 must be converted to REAL8 for sprintf
LOCAL dblValue:REAL8
finit
fld fltValue
fstp dblValue
fwait
invoke crt_sprintf, lpBuffer, SADD("%lf"), dblValue
ret
FltToStr ENDP
;======================================================
DblToStr PROC dblValue:REAL8, lpBuffer:PTR BYTE
invoke crt_sprintf, lpBuffer, SADD("%lf"), dblValue
ret
DblToStr ENDP
;======================================================
LdblToStr PROC ldblValue:REAL10, lpBuffer:PTR BYTE
; REAL10 must be converted to REAL8 for sprintf
; A 'long double' in MS C/C++ is a REAL8
LOCAL dblValue:REAL8
finit
fld ldblValue
fstp dblValue
fwait
invoke crt_sprintf, lpBuffer, SADD("%lf"), dblValue
ret
LdblToStr ENDP
;======================================================
WaitKey PROC
invoke crt_printf, SADD(13,10,"Press any key to continue...")
invoke crt__getch
.if (eax == 0) || (eax == 0E0h)
invoke crt__getch
.endif
invoke crt_printf, SADD(13,10)
ret
WaitKey ENDP
;======================================================
END start
Thanks Greg,
This is great stuff and very useful. :U
Hutch,
You know, using the Pelle's C run-time library would have some advantages over the MSVC run-time library as Pelle's C supports most, if not all, of the new C99 functions and MSVC does not support them at all. There are several new and very useful C99 functions that could be used in MASM32.
I wonder if that would be OK with Pelle?
Greg,
Let me try this out on you, the set of functions are very good as they make a very good range of functionality available but there is no good reason to place tem in a seperate procedure when almost all of them make a direct call to MSVCRT.DLL. What I think would be more useful as we already have the C runtime functions available would be to make a set of direct macros using this excellent range of functionality so they could be used in a closer to "function" format with return values much like a higher level language.
Its basically the idea that we have the preprocessor available so we may as well make use of it.
Now another question as you have the expertise in C runtime library functions, are the versions using the "sprintf" function as fast as the dedicated conversions in msvcrt like _ltoa or similar ?
Hutch,
Yeah, I know, macros would be better. I thought about writing these procedures as macros, but I'm lousy at macros. I guess I need to get better at them. I'm just passing along ideas.
As far as the speed compared to ltoa etc., I don't know, I would have to do some testing. I do know the floating-point conversion functions like fcvt are pretty lousy because they don't store the decimal point in the string, you would need an additional routine to insert the decimal point into the string. itoa and ltoa are fine.
I've never been greatly concerned about speed unless it's really needed in the routine. I'm more interested in the functionality, simplicity and elegance of the code.
I do need to learn how to write macros better ...
Greg,
See what you think of these, I have not put them in a test piece but I think they are OK.
;; ----------------------------------------------
byte$ MACRO bytevalue
LOCAL buffer
.data?
buffer db 4 dup (?)
.code
fn crt_sprintf,OFFSET Buffer,"%u",bytevalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sbyte$ MACRO bytevalue
LOCAL buffer
.data?
buffer db 4 dup (?)
.code
fn crt_sprintf,OFFSET Buffer,"%d",bytevalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
:P
Here is my C run-time example :
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include crtdll.inc
includelib \masm32\lib\kernel32.lib
includelib crtdll.lib
.data
msg db "Command=%s P1=%s P2=%s",0
errmsg db "Only two parameters!",0
.data?
argc dd ?
argv dd ?
env dd ?
.code
start:
invoke __GetMainArgs,ADDR argc,ADDR argv,ADDR env,0
cmp argc,3
jne error
mov edx,argv
invoke printf,ADDR msg,dword ptr [edx],dword ptr [edx+4],dword ptr [edx+8]
finish:
invoke ExitProcess,0
error:
invoke printf,ADDR errmsg
jmp finish
END start
[attachment deleted by admin]
Looks good Erol. :U
'Number to string' macros that use the C Run-time sprintf function. I had nothing but problems using fn or invoke in these macros, I never figured out why, I did it the direct way and it worked. Any suggestions for improvement are welcome.
.586
.MODEL FLAT, stdcall
option casemap:none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\masm32.inc
include c:\masm32\include\msvcrt.inc
include c:\masm32\macros\macros.asm
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\masm32.lib
includelib c:\masm32\lib\msvcrt.lib
;; ----------------------------------------------
ubyte$ MACRO ubytevalue:req
; unsigned byte
LOCAL buffer
.data?
buffer BYTE 4 dup(?)
IFNDEF ubfmt
.data
ubfmt BYTE "%hhu", 0
ENDIF
.code
mov al, ubytevalue
movzx eax, al
push eax
push OFFSET ubfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sbyte$ MACRO sbytevalue:req
; signed byte
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF sbfmt
.data
sbfmt BYTE "%hhd", 0
ENDIF
.code
mov al, sbytevalue
movsx eax, al
push eax
push OFFSET sbfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xbyte$ MACRO xbytevalue:req
; unsigned hex byte
LOCAL buffer
.data?
buffer BYTE 4 dup(?)
IFNDEF xbfmt
.data
xbfmt BYTE "%hhX", 0
ENDIF
.code
mov al, xbytevalue
movzx eax, al
push eax
push OFFSET xbfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uword$ MACRO uwordvalue:req
; unsigned word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF uwfmt
.data
uwfmt BYTE "%hu", 0
ENDIF
.code
mov ax, uwordvalue
movzx eax, ax
push eax
push OFFSET uwfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sword$ MACRO swordvalue:req
; signed word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF swfmt
.data
swfmt BYTE "%hd", 0
ENDIF
.code
mov ax, swordvalue
movsx eax, ax
push eax
push OFFSET swfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xword$ MACRO xwordvalue:req
; unsigned hex word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF xwfmt
.data
xwfmt BYTE "%hX", 0
ENDIF
.code
mov ax, xwordvalue
movzx eax, ax
push eax
push OFFSET xwfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
udword$ MACRO udwordvalue:req
; unsigned dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF udwfmt
.data
udwfmt BYTE "%lu", 0
ENDIF
.code
push udwordvalue
push OFFSET udwfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sdword$ MACRO sdwordvalue:req
; signed dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF sdwfmt
.data
sdwfmt BYTE "%ld", 0
ENDIF
.code
push sdwordvalue
push OFFSET sdwfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xdword$ MACRO xdwordvalue:req
; unsigned hex dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF xdwfmt
.data
xdwfmt BYTE "%lX", 0
ENDIF
.code
push xdwordvalue
push OFFSET xdwfmt
push OFFSET buffer
call crt_sprintf
add esp, 12
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uqword$ MACRO uqwordvalue:req
; unsigned qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF uqwfmt
.data
uqwfmt BYTE "%I64u", 0
ENDIF
.code
push DWORD PTR [uqwordvalue+4]
push DWORD PTR [uqwordvalue+0]
push OFFSET uqwfmt
push OFFSET buffer
call crt_sprintf
add esp, 16
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sqword$ MACRO sqwordvalue:req
; signed qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF sqwfmt
.data
sqwfmt BYTE "%I64d", 0
ENDIF
.code
push SDWORD PTR [sqwordvalue+4]
push DWORD PTR [sqwordvalue+0]
push OFFSET sqwfmt
push OFFSET buffer
call crt_sprintf
add esp, 16
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xqword$ MACRO xqwordvalue:req
; unsigned hex qword
LOCAL buffer
.data?
buffer BYTE 20 dup(?)
IFNDEF xqwfmt
.data
xqwfmt BYTE "%I64X", 0
ENDIF
.code
push DWORD PTR [xqwordvalue+4]
push DWORD PTR [xqwordvalue+0]
push OFFSET xqwfmt
push OFFSET buffer
call crt_sprintf
add esp, 16
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real4$ MACRO r4value:req
LOCAL r8value, buffer
.data?
r8value REAL8 ?
buffer BYTE 52 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
finit
fld r4value
fstp r8value
fwait
push DWORD PTR [r8value+4]
push DWORD PTR [r8value+0]
push OFFSET r8fmt
push OFFSET buffer
call crt_sprintf
add esp, 16
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real8$ MACRO r8value:req
LOCAL buffer
.data?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
push DWORD PTR [r8value+4]
push DWORD PTR [r8value+0]
push OFFSET r8fmt
push OFFSET buffer
call crt_sprintf
add esp, 16
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real10$ MACRO r10value:req
LOCAL r8value, buffer
.data?
r8value REAL8 ?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
finit
fld r10value
fstp r8value
fwait
push DWORD PTR [r8value+4]
push DWORD PTR [r8value+0]
push OFFSET r8fmt
push OFFSET buffer
call crt_sprintf
add esp, 16
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
WaitKey MACRO
LOCAL crlf, pak
.data
crlf BYTE 13, 10, 0
pak BYTE 13, 10, "Press any key to continue...", 0
.code
push OFFSET pak
call crt_printf
add esp, 4
call crt__getch
.if (eax == 0) || (eax == 0E0h)
call crt__getch
.endif
push OFFSET crlf
call crt_printf
add esp, 4
ENDM
;; ----------------------------------------------
.DATA
ubyMax BYTE 255 ; UCHAR_MAX
ubyMin BYTE 0 ; UCHAR_MIN
sbyMax SBYTE 127 ; CHAR_MAX
sbyMin SBYTE -128 ; CHAR_MIN
uwdMax WORD 65535 ; USHRT_MAX
uwdMin WORD 0 ; USHRT_MIN
swdMax SWORD 32767 ; SHRT_MAX
swdMin SWORD -32768 ; SHRT_MIN
udwMax DWORD 4294967295 ; ULONG MAX
udwMin DWORD 0 ; ULONG_MIN
sdwMax SDWORD 2147483647 ; LONG_MAX
sdwMin SDWORD -2147483647 - 1 ; LONG_MIN
uqwMax QWORD 18446744073709551615 ; _UI64_MAX
uqwMin QWORD 0 ; _UI64_MIN
sqwMax QWORD 9223372036854775807 ; _I64_MAX
sqwMin QWORD -9223372036854775807 - 1 ; _I64_MIN
r4Max REAL4 3.402823466E+38 ; FLT_MAX
r4Min REAL4 -1.175494351E-38 ; FLT_MIN
r8Max REAL8 1.7976931348623158E+308 ; DBL_MAX
r8Min REAL8 -2.2250738585072014E-308 ; DBL_MIN
r10Max REAL10 1.7976931348623158E+308 ; LDBL_MAX ( = DBL_MAX)
r10Min REAL10 -2.2250738585072014E-308 ; LDBL_MIN ( = DBL_MIN)
.CODE
start:
invoke crt_puts, ubyte$(ubyMax)
invoke crt_puts, ubyte$(ubyMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sbyte$(sbyMax)
invoke crt_puts, sbyte$(sbyMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xbyte$(ubyMax)
invoke crt_puts, xbyte$(ubyMin)
invoke crt_puts, SADD(0)
invoke crt_puts, uword$(uwdMax)
invoke crt_puts, uword$(uwdMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sword$(swdMax)
invoke crt_puts, sword$(swdMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xword$(uwdMax)
invoke crt_puts, xword$(uwdMin)
invoke crt_puts, SADD(0)
invoke crt_puts, udword$(udwMax)
invoke crt_puts, udword$(udwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sdword$(sdwMax)
invoke crt_puts, sdword$(sdwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xdword$(udwMax)
invoke crt_puts, xdword$(udwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, uqword$(uqwMax)
invoke crt_puts, uqword$(uqwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sqword$(sqwMax)
invoke crt_puts, sqword$(sqwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xqword$(uqwMax)
invoke crt_puts, xqword$(uqwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, real4$(r4Max)
invoke crt_puts, real4$(r4Min)
invoke crt_puts, SADD(0)
invoke crt_puts, real8$(r8Max)
invoke crt_puts, real8$(r8Min)
invoke crt_puts, SADD(0)
invoke crt_puts, real10$(r10Max)
invoke crt_puts, real10$(r10Min)
WaitKey
invoke ExitProcess, 0
end start
Greg,
These look good and should be a great improvement in the handling of such a wide range of data types. If you are happy enough that they are reliable, I would like to add them to the macros.asm file for MASM32 as I have been doing a lot of work on it recently myself.
Hutch,
You bet. But I haven't done any testing of them besides the program I posted. I made a few corrections a few minutes after I first posted it, cleanup stuff, so get the latest version. Maybe some other folks will try them out.
Any idea why I was having problems using fn and invoke in the macros?
Greg,
I am not sure but I notice that you are zero extending the byte values to DWORD so the invoke syntax may not be handling this correctly. The "fn" macro uses invoke.
Hutch,
Yeah, that's where the problem was, invoke was not handling it correctly. Same thing with some of the other types.
I did it this way so that you would get an assembly error if you passed anything other than the correct type:
mov al, bytevalue
movzx eax, al
push eax
instead of
movzx eax, bytevalue
Yes,
I got the same results. I played with the first two and you can use invoke with them if you extend the data size to DWORD. Here are the two tested versions. There is no reason to change the manual layout as invoke does not do it any better but I have one suggestion, the format strings only need to be defined once so I put them in an IFNDEF block and reduced the buffer data sizes to the next aligned size after the maximum size that can be written to the data in characters.
ubyte$ MACRO bytevalue:req
LOCAL buffer
.data?
buffer db 4 dup (?)
IFNDEF ubfmt
.data
ubfmt db "%u",0
ENDIF
.code
mov al, bytevalue
movzx eax, al
invoke crt_sprintf,OFFSET buffer,OFFSET ubfmt,eax
EXITM <OFFSET buffer>
ENDM
sbyte$ MACRO bytevalue:req
LOCAL buffer
.data?
buffer db 4 dup (?)
IFNDEF sbfmt
.data
sbfmt db "%d",0
ENDIF
.code
mov al, bytevalue
movsx eax, al
invoke crt_sprintf,OFFSET buffer,OFFSET sbfmt,eax
EXITM <OFFSET buffer>
ENDM
Hutch,
Quote
IFNDEF
good idea
For
sbyte$ max bytes is
5 ("-128",0) :bg
tomorrow ...
Yep,
You are right actually, problem is with counting with your fingers is you don't get signed values all that well. :red
I updated the 'number to string' macros in my earlier post as there were some problems I discovered. The qwords were being pushed incorrectly. I also added the IFNDEF for the format strings, changed the buffer lengths and added some more testing.
I deleted a question that was here because the light just went on, doh! I need to take a break. ::)
Greg,
These are testing up fine at this end, they look like they are ready to rock 'n roll.
Hutch,
I think so.
String input using C Run-Time Library fgets function.
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
;include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm
includelib kernel32.lib
includelib user32.lib
;includelib masm32.lib
includelib msvcrt.lib
;-----------------------------------------------------
linein MACRO
LOCAL buffer
.data?
buffer BYTE 96 dup(?)
IFNDEF strFmt
.data
strFmt BYTE "%s", 0
ENDIF
IFNDEF Cr
.data
dwNewLine DWORD 10
ENDIF
.code
invoke crt_fgets, ADDR buffer, SIZEOF buffer, crt__iob
push eax
invoke crt_strchr, ADDR buffer, dwNewLine
.if eax
mov BYTE PTR [eax], 0 ; remove the newline from buffer
.endif
pop eax
EXITM <eax>
ENDM
;-----------------------------------------------------
WaitKey MACRO
LOCAL crlf, pak
.data
szCrLf BYTE 13, 10, 0
szPAK BYTE 13, 10, "Press any key to continue...", 0
.code
invoke crt_printf, ADDR szPAK
call crt__getch
.if (eax == 0) || (eax == 0E0h)
call crt__getch
.endif
invoke crt_printf, ADDR szCrLf
ENDM
;-----------------------------------------------------
.DATA
lpInput DWORD 0
msg1 BYTE "Enter some text: ", 0
msg2 BYTE "You entered: %s", 13, 10, 0
.CODE
start:
invoke crt_printf, ADDR msg1
mov lpInput, linein()
invoke crt_printf, ADDR msg2, lpInput
WaitKey
invoke ExitProcess, 0
end start
'String to number' procedures using the C Run-Time function sscanf.
; 'String to number' procedures that use
; the C Run-Time Library function sscanf.
; Greg Lyon - 2005
;
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm ; uses the file dated July 10, 2005
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib msvcrt.lib
atoubyte PROTO pStr:PTR BYTE, pUb :PTR BYTE
atosbyte PROTO pStr:PTR BYTE, pSb :PTR SBYTE
ahextoubyte PROTO pStr:PTR BYTE, pUb :PTR BYTE
atouword PROTO pStr:PTR BYTE, pUw :PTR WORD
atosword PROTO pStr:PTR BYTE, pSw :PTR SWORD
ahextouword PROTO pStr:PTR BYTE, pUw :PTR WORD
atoudword PROTO pStr:PTR BYTE, pUdw:PTR DWORD
atosdword PROTO pStr:PTR BYTE, pSdw:PTR SDWORD
ahextoudword PROTO pStr:PTR BYTE, pUdw:PTR DWORD
atouqword PROTO pStr:PTR BYTE, pUqw:PTR QWORD
atosqword PROTO pStr:PTR BYTE, pSqw:PTR QWORD
ahextouqword PROTO pStr:PTR BYTE, pUqw:PTR QWORD
atoreal4 PROTO pStr:PTR BYTE, pR4 :PTR REAL4
atoreal8 PROTO pStr:PTR BYTE, pR8 :PTR REAL8
atoreal10 PROTO pStr:PTR BYTE, pR10:PTR REAL10
.DATA
szUbyMin BYTE "0",0
szUbyMax BYTE "255",0
szSbyMin BYTE "-128",0
szSbyMax BYTE "127",0
szUwdMin BYTE "0",0
szUwdMax BYTE "65535",0
szSwdMin BYTE "-32768",0
szSwdMax BYTE "32767",0
szUdwMin BYTE "0",0
szUdwMax BYTE "4294967295",0
szSdwMin BYTE "-2147483648",0
szSdwMax BYTE "2147483647",0
szUqwMin BYTE "0",0
szUqwMax BYTE "18446744073709551615",0
szSqwMin BYTE "-9223372036854775808",0
szSqwMax BYTE "9223372036854775807",0
szR4Min BYTE "-1.175494351E-38",0
szR4Max BYTE "3.402823466E+38",0
szR8Min BYTE "-2.2250738585072014E-308",0
szR8Max BYTE "1.7976931348623158E+308",0
szR10Min BYTE "-2.2250738585072014E-308",0
szR10Max BYTE "1.7976931348623158E+308",0
szHexBy BYTE "FF", 0
szHexWd BYTE "FFFF", 0
szHexDw BYTE "FFFFFFFF", 0
szHexQw BYTE "FFFFFFFFFFFFFFFF", 0
align 16
r10Max REAL10 0.0
r10Min REAL10 0.0
r8Max REAL8 0.0
r8Min REAL8 0.0
r4Max REAL4 0.0
r4Min REAL4 0.0
uqwMax QWORD 0
uqwMin QWORD 0
sqwMax QWORD 0
sqwMin QWORD 0
udwMax DWORD 0
udwMin DWORD 0
sdwMax SDWORD 0
sdwMin SDWORD 0
uwdMax WORD 0
uwdMin WORD 0
swdMax SWORD 0
swdMin SWORD 0
ubyMax BYTE 0
ubyMin BYTE 0
sbyMax SBYTE 0
sbyMin SBYTE 0
.CODE
start:
; decimal string to unsigned byte
invoke atoubyte, ADDR szUbyMax, ADDR ubyMax
print ubyte$(ubyMax)
print SADD(13,10)
invoke atoubyte, ADDR szUbyMin, ADDR ubyMin
print ubyte$(ubyMin)
print SADD(13,10,13,10)
; decimal string to signed byte
invoke atosbyte, ADDR szSbyMax, ADDR sbyMax
print sbyte$(sbyMax)
print SADD(13,10)
invoke atosbyte, ADDR szSbyMin, ADDR sbyMin
print sbyte$(sbyMin)
print SADD(13,10,13,10)
; hex string to unsigned byte
invoke ahextoubyte, ADDR szHexBy, ADDR ubyMax
print xbyte$(ubyMax)
print SADD(13,10)
invoke ahextoubyte, ADDR szUbyMin, ADDR ubyMin
print xbyte$(ubyMin)
print SADD(13,10,13,10)
; decimal string to unsigned word
invoke atouword, ADDR szUwdMax, ADDR uwdMax
print uword$(uwdMax)
print SADD(13,10)
invoke atouword, ADDR szUwdMin, ADDR uwdMin
print uword$(uwdMin)
print SADD(13,10,13,10)
; decimal string to signed word
invoke atosword, ADDR szSwdMax, ADDR swdMax
print sword$(swdMax)
print SADD(13,10)
invoke atosword, ADDR szSwdMin, ADDR swdMin
print sword$(swdMin)
print SADD(13,10,13,10)
; hex string to unsigned word
invoke ahextouword, ADDR szHexWd, ADDR uwdMax
print xword$(uwdMax)
print SADD(13,10)
invoke ahextouword, ADDR szUwdMin, ADDR uwdMin
print xword$(uwdMin)
print SADD(13,10,13,10)
; decimal string to unsigned dword
invoke atoudword, ADDR szUdwMax, ADDR udwMax
print udword$(udwMax)
print SADD(13,10)
invoke atoudword, ADDR szUdwMin, ADDR udwMin
print udword$(udwMin)
print SADD(13,10,13,10)
; decimal string to signed dword
invoke atosdword, ADDR szSdwMax, ADDR sdwMax
print sdword$(sdwMax)
print SADD(13,10)
invoke atosdword, ADDR szSdwMin, ADDR sdwMin
print sdword$(sdwMin)
print SADD(13,10,13,10)
; hex string to unsigned dword
invoke ahextoudword, ADDR szHexDw, ADDR udwMax
print xdword$(udwMax)
print SADD(13,10)
invoke ahextoudword, ADDR szUdwMin, ADDR udwMin
print xdword$(udwMin)
print SADD(13,10,13,10)
; decimal string to unsigned qword
invoke atouqword, ADDR szUqwMax, ADDR uqwMax
print uqword$(uqwMax)
print SADD(13,10)
invoke atouqword, ADDR szUqwMin, ADDR uqwMin
print uqword$(uqwMin)
print SADD(13,10,13,10)
; decimal string to signed qword
invoke atosqword, ADDR szSqwMax, ADDR sqwMax
print sqword$(sqwMax)
print SADD(13,10)
invoke atosqword, ADDR szSqwMin, ADDR sqwMin
print sqword$(sqwMin)
print SADD(13,10,13,10)
; hex string to unsigned qword
invoke ahextouqword, ADDR szHexQw, ADDR uqwMax
print xqword$(uqwMax)
print SADD(13,10)
invoke ahextouqword, ADDR szUqwMin, ADDR uqwMin
print xqword$(uqwMin)
print SADD(13,10,13,10)
; real4
invoke atoreal4, ADDR szR4Max, ADDR r4Max
print real4$(r4Max)
print SADD(13,10)
invoke atoreal4, ADDR szR4Min, ADDR r4Min
print real4$(r4Min)
print SADD(13,10,13,10)
; real8
invoke atoreal8, ADDR szR8Max, ADDR r8Max
print real8$(r8Max)
print SADD(13,10)
invoke atoreal8, ADDR szR8Min, ADDR r8Min
print real8$(r8Min)
print SADD(13,10,13,10)
; real10
invoke atoreal10, ADDR szR10Max, ADDR r10Max
print real10$(r10Max)
print SADD(13,10)
invoke atoreal10, ADDR szR10Min, ADDR r10Min
print real10$(r10Min)
print SADD(13,10,13,10)
invoke ExitProcess, 0
;---------------------------------------
atoubyte PROC pStr:PTR BYTE, pUb:PTR BYTE
.data
align 4
ub DWORD 0
IFNDEF ubfmt
.data
ubfmt BYTE "%hhu",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR ubfmt, ADDR ub
mov eax, ub
mov edx, pUb
mov BYTE PTR [edx], al
ret
atoubyte ENDP
;---------------------------------------
atosbyte PROC pStr:PTR BYTE, pSb:PTR SBYTE
IFNDEF sb
.data
align 4
sb SDWORD 0
ENDIF
IFNDEF sbfmt
.data
sbfmt BYTE "%hhd",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR sbfmt, ADDR sb
mov eax, sb
mov edx, pSb
mov SBYTE PTR [edx], al
ret
atosbyte ENDP
;---------------------------------------
ahextoubyte PROC pStr:PTR BYTE, pUb:PTR BYTE
IFNDEF ub
.data
align 4
ub DWORD 0
ENDIF
IFNDEF xbfmt
.data
xbfmt BYTE "%hhX",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR xbfmt, ADDR ub
mov eax, ub
mov edx, pUb
mov BYTE PTR [edx], al
ret
ahextoubyte ENDP
;---------------------------------------
atouword PROC pStr:PTR BYTE, pUw:PTR WORD
IFNDEF uw
.data
align 4
uw DWORD 0
ENDIF
IFNDEF uwfmt
.data
uwfmt BYTE "%hu",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR uwfmt, ADDR uw
mov eax, uw
mov edx, pUw
mov WORD PTR [edx], ax
ret
atouword ENDP
;---------------------------------------
atosword PROC pStr:PTR BYTE, pSw:PTR SWORD
IFNDEF sw
.data
align 4
sw SDWORD 0
ENDIF
IFNDEF swfmt
.data
swfmt BYTE "%hd",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR swfmt, ADDR sw
mov eax, sw
mov edx, pSw
mov SWORD PTR [edx], ax
ret
atosword ENDP
;---------------------------------------
ahextouword PROC pStr:PTR BYTE, pUw:PTR WORD
IFNDEF uw
.data
align 4
uw DWORD 0
ENDIF
IFNDEF xwfmt
.data
xwfmt BYTE "%hX",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR xwfmt, ADDR uw
mov eax, uw
mov edx, pUw
mov WORD PTR [edx], ax
ret
ahextouword ENDP
;---------------------------------------
atoudword PROC pStr:PTR BYTE, pUdw:PTR DWORD
IFNDEF udw
.data
align 4
udw DWORD 0
ENDIF
IFNDEF udwfmt
.data
udwfmt BYTE "%u",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR udwfmt, ADDR udw
mov eax, udw
mov edx, pUdw
mov DWORD PTR [edx], eax
ret
atoudword ENDP
;---------------------------------------
atosdword PROC pStr:PTR BYTE, pSdw:PTR SDWORD
IFNDEF sdw
.data
align 4
sdw SDWORD 0
ENDIF
IFNDEF sdwfmt
.data
sdwfmt BYTE "%d",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR sdwfmt, ADDR sdw
mov eax, sdw
mov edx, pSdw
mov SDWORD PTR [edx], eax
ret
atosdword ENDP
;---------------------------------------
ahextoudword PROC pStr:PTR BYTE, pUdw:PTR DWORD
IFNDEF udw
.data
align 4
udw DWORD 0
ENDIF
IFNDEF xdwfmt
.data
xdwfmt BYTE "%X",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR xdwfmt, ADDR udw
mov eax, udw
mov edx, pUdw
mov DWORD PTR [edx], eax
ret
ahextoudword ENDP
;---------------------------------------
atouqword PROC pStr:PTR BYTE, pUqw:PTR QWORD
IFNDEF uqw
.data
align 8
uqw QWORD 0
ENDIF
IFNDEF uqwfmt
.data
uqwfmt BYTE "%I64u",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR uqwfmt, ADDR uqw
mov edx, pUqw
mov eax, DWORD PTR [uqw+4]
mov DWORD PTR [edx+4], eax
mov eax, DWORD PTR [uqw+0]
mov DWORD PTR [edx+0], eax
ret
atouqword ENDP
;---------------------------------------
atosqword PROC pStr:PTR BYTE, pSqw:PTR QWORD
IFNDEF sqw
.data
align 8
sqw QWORD 0
ENDIF
IFNDEF sqwfmt
.data
sqwfmt BYTE "%I64d",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR sqwfmt, ADDR sqw
mov edx, pSqw
mov eax, DWORD PTR [sqw+4]
mov DWORD PTR [edx+4], eax
mov eax, DWORD PTR [sqw+0]
mov DWORD PTR [edx+0], eax
ret
atosqword ENDP
;-------------------------------------------
ahextouqword PROC pStr:PTR BYTE, pUqw:PTR QWORD
IFNDEF uqw
.data
align 8
uqw QWORD 0
ENDIF
IFNDEF xqwfmt
.data
xqwfmt BYTE "%I64X",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR xqwfmt, ADDR uqw
mov edx, pUqw
mov eax, DWORD PTR [uqw+4]
mov DWORD PTR [edx+4], eax
mov eax, DWORD PTR [uqw+0]
mov DWORD PTR [edx+0], eax
ret
ahextouqword ENDP
;---------------------------------------
atoreal4 PROC pStr:PTR BYTE, pR4:PTR REAL4
IFNDEF r4fmt
.data
r4fmt BYTE "%f",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR r4fmt, pR4
ret
atoreal4 ENDP
;-------------------------------------------
atoreal8 PROC pStr:PTR BYTE, pR8:PTR REAL8
IFNDEF r8fmt
.data
r8fmt BYTE "%lf",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR r8fmt, pR8
ret
atoreal8 ENDP
;--------------------------------------------
atoreal10 PROC pStr:PTR BYTE, pR10:PTR REAL10
IFNDEF r8
.data
align 8
r8 REAL8 0.0
ENDIF
IFNDEF r8fmt
.data
r8fmt BYTE "%lf",0
ENDIF
.code
invoke crt_sscanf, pStr, ADDR r8fmt, ADDR r8
mov eax, pR10
finit
fld r8
fstp REAL10 PTR [eax]
ret
atoreal10 ENDP
;--------------------------------------------
end start
[attachment deleted by admin]
Updated above, changed data in procedures to LOCAL.
Updated again, eliminated unnecessary code.
I'm done for tonight. :bg
Updated again, added hex string support.
Thanks Greg,
Another masterpiece. :U
This is an unrelated question. Whenever I have tried to use the internal notation within a quoted string of \t or \n I only ever get those characters as literals in the displayed string and not the TAB or CRLF. Am I missing something in how I format the strings.
fn crt_printf,"%s","This \t is a test\n"
I have checked the macros 1st pass and the normal string content is written to the .DATA section so it is passed as a normal zero terminated string.
Hutch,
I know, I ran into the same thing. I thought that would work too, but it doesn't. I think the C compiler replaces the "\t" or "\n" in the string with the actual character code like 9 or 10 at compile-time, so in MASM we have to manually do what the C compiler automatically does.
More input/output macros that use C run-time library functions. I know MASM32 already has macros that do these things. I'm just exploring what can be done with the CRT functions in MASM32.
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib msvcrt.lib
;-----------------------------------------------------
linein MACRO
; Usage: mov eax, linein()
LOCAL buffer
.data?
buffer BYTE 96 dup(?)
IFNDEF strFmt
.data
strFmt BYTE "%s", 0
ENDIF
IFNDEF Cr
.data
dwNewLine DWORD 10
ENDIF
.code
invoke crt_fgets, ADDR buffer, SIZEOF buffer, crt__iob
push eax
invoke crt_strchr, ADDR buffer, dwNewLine
.if eax
mov BYTE PTR [eax], 0 ; remove the newline from buffer
.endif
pop eax
EXITM <eax>
ENDM
;-----------------------------------------------------
lineout MACRO pStr
; Usage: lineout chr$("Hello")
; Replaces the trailing nul character
; with a linefeed.
IFNB <pStr>
invoke crt_puts, pStr
ELSE
invoke crt_puts, chr$(0)
ENDIF
ENDM
;-----------------------------------------------------
printstr MACRO pStr:req
;Usage: printstr chr$("Hello")
invoke crt_printf, pStr
ENDM
;-----------------------------------------------------
WaitKey MACRO
lineout
printstr chr$("Press any key to exit...")
invoke wait_key
lineout
ENDM
;-----------------------------------------------------
.DATA
lpInput DWORD 0
.CODE
start:
lineout chr$("Input/Output Test")
lineout
printstr chr$("Enter some text: ")
mov lpInput, linein()
printstr chr$("You entered: ")
lineout lpInput
WaitKey
invoke ExitProcess, 0
end start
An example of using floating-point CRT functions.
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib msvcrt.lib
Sine PROTO degrees:PTR REAL8, sin:PTR REAL8
Cosine PROTO degrees:PTR REAL8, cos:PTR REAL8
Tangent PROTO degrees:PTR REAL8, tan:PTR REAL8
Arcsine PROTO sin :PTR REAL8, degrees:PTR REAL8
Arccosine PROTO cos :PTR REAL8, degrees:PTR REAL8
Arctangent PROTO tan :PTR REAL8, degrees:PTR REAL8
inputR4 PROTO pR4 :PTR REAL4
inputR8 PROTO pR8 :PTR REAL8
inputR10 PROTO pR10 :PTR REAL10
;---------------------------------------
; macros
;---------------------------------------
printf MACRO pszFmt, args:VARARG
IFB <args>
IFB <pszFmt>
invoke crt_printf, chr$(13,10)
ELSE
invoke crt_printf, pszFmt
ENDIF
ELSE
invoke crt_printf, pszFmt, args
ENDIF
ENDM
;---------------------------------------
WaitKey MACRO
printf chr$(13,10,"Press any key to continue...")
call wait_key
printf
ENDM
;---------------------------------------
.DATA
dblDegrees REAL8 0.0
dblSine REAL8 0.0
dblCosine REAL8 0.0
dblTangent REAL8 0.0
.CODE
start:
printf chr$("Enter angle in degrees: ")
invoke inputR8, ADDR dblDegrees
printf
printf chr$("Angle = %9.6lf degrees",13, 10), dblDegrees
invoke Sine, ADDR dblDegrees, ADDR dblSine
invoke Cosine, ADDR dblDegrees, ADDR dblCosine
invoke Tangent, ADDR dblDegrees, ADDR dblTangent
printf
printf chr$("Sine(%.0lf) = %8.6lf", 13, 10), dblDegrees, dblSine
printf chr$("Cosine(%.0lf) = %8.6lf", 13, 10), dblDegrees, dblCosine
printf chr$("Tangent(%.0lf) = %8.6lf", 13, 10), dblDegrees, dblTangent
printf
invoke Arcsine, ADDR dblSine, ADDR dblDegrees
printf chr$("Arcsine(%8.6lf) = %9.6lf degrees", 13, 10), dblSine, dblDegrees
invoke Arccosine, ADDR dblCosine, ADDR dblDegrees
printf chr$("Arccosine(%8.6lf) = %9.6lf degrees", 13, 10), dblCosine, dblDegrees
invoke Arctangent, ADDR dblTangent, ADDR dblDegrees
printf chr$("Arctangent(%8.6lf) = %9.6lf degrees", 13, 10), dblTangent, dblDegrees
WaitKey
invoke ExitProcess, 0
;======================================================
; procedures
;======================================================
Sine PROC degrees:PTR REAL8, sin:PTR REAL8
LOCAL radians:REAL8
IFNDEF DtoR
.CONST
DtoR REAL10 0.017453292519943295769 ;(Pi / 180.0)
ENDIF
.CODE
mov eax, degrees
finit
fld REAL8 PTR [eax]
fld DtoR
fmul
fstp radians
fwait
invoke crt_sin, radians ;result in ST(0)
mov eax, sin
fstp REAL8 PTR [eax]
fwait
ret
Sine ENDP
;======================================================
Cosine PROC degrees:PTR REAL8, cos:PTR REAL8
LOCAL radians:REAL8
IFNDEF DtoR
.CONST
DtoR REAL10 0.017453292519943295769 ;(Pi / 180.0)
ENDIF
.CODE
mov eax, degrees
finit
fld REAL8 PTR [eax]
fld DtoR
fmul
fstp radians
fwait
invoke crt_cos, radians ;result in ST(0)
mov eax, cos
fstp REAL8 PTR [eax]
fwait
ret
Cosine ENDP
;======================================================
Tangent PROC degrees:PTR REAL8, tan:PTR REAL8
LOCAL radians:REAL8
IFNDEF DtoR
.CONST
DtoR REAL10 0.017453292519943295769 ;(Pi / 180.0)
ENDIF
.CODE
mov eax, degrees
finit
fld REAL8 PTR [eax]
fld DtoR
fmul
fstp radians
fwait
invoke crt_tan, radians ;result in ST(0)
mov eax, tan
fstp REAL8 PTR [eax]
fwait
ret
Tangent ENDP
;======================================================
Arcsine PROC sin:PTR REAL8, degrees:PTR REAL8
IFNDEF RtoD
.CONST
RtoD REAL10 57.295779513082320876798 ;(180.0 / Pi)
ENDIF
.CODE
mov eax, sin
invoke crt_asin, REAL8 PTR [eax] ;result in ST(0)
mov eax, degrees
fld RtoD
fmul
fstp REAL8 PTR [eax]
fwait
ret
Arcsine ENDP
;======================================================
Arccosine PROC cos:PTR REAL8, degrees:PTR REAL8
IFNDEF RtoD
.CONST
RtoD REAL10 57.295779513082320876798 ;(180.0 / Pi)
ENDIF
.CODE
mov eax, cos
invoke crt_acos, REAL8 PTR [eax] ;result in ST(0)
mov eax, degrees
fld RtoD
fmul
fstp REAL8 PTR [eax]
fwait
ret
Arccosine ENDP
;======================================================
Arctangent PROC tan:PTR REAL8, degrees:PTR REAL8
IFNDEF RtoD
.CONST
RtoD REAL10 57.295779513082320876798 ;(180.0 / Pi)
ENDIF
.CODE
mov eax, tan
invoke crt_atan, REAL8 PTR [eax] ;result in ST(0)
mov eax, degrees
fld RtoD
fmul
fstp REAL8 PTR [eax]
fwait
ret
Arctangent ENDP
;======================================================
inputR4 PROC pR4:PTR REAL4
invoke crt_scanf, chr$("%f"), pR4
ret
inputR4 ENDP
;======================================================
inputR8 PROC pR8:PTR REAL8
invoke crt_scanf, chr$("%lf"), pR8
ret
inputR8 ENDP
;======================================================
inputR10 PROC pR10:PTR REAL10
LOCAL r8:REAL8
invoke crt_scanf, chr$("%lf"), ADDR r8
mov eax, pR10
finit
fld r8
fstp REAL10 PTR [eax]
fwait
ret
inputR10 ENDP
;======================================================
end start
Nice work.
Technically, what's the difference between msvcrt.dll and crtdll.dll ?
Vortex,
Good question, it's not real clear in my mind either. Here are some links I found:
How To Use the C Run-Time (http://support.microsoft.com/default.aspx?scid=kb;en-us;94248)
How to link with the correct C Run-Time (CRT) library (http://support.microsoft.com/default.aspx?scid=kb;en-us;140584)
C Run-Time Libraries (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_CRT_C_Run.2d.Time_Libraries.asp)
I have also been trying out using pocrt, as it supports some handy C99 stuff that msvcrt does not. With it we're back to the naming conflicts problem with the MASM32 library. How Hutch modified the .inc and .lib for msvcrt is beyond me at this point.
Hutch (and anyone else that is interested),
Regarding the sprintf conversion macros:
Something I noticed, if a REAL8 is passed to the real4$ macro, the buffer could possibly overflow since the REAL4 gets converted to a REAL8 for the function call. Why am I dong this? Because the C compiler generates code to convert the REAL4 to a REAL8 for the call to sprintf. If you pass sprintf a REAL4 it doesn't work correctly.
1) increase the buffer to 320 to allow for DBL_MAX.
2) Somehow only allow a REAL4 to be passed to the macro.
I also noticed that the buffers in all the macros should be initialized to 0 so that if sprintf fails the buffer string is terminated.
What do you think? I'd be glad to make these changes.
Greg,
There is a toy in the latest service pack that solves this problem. MAKECIMP will work on any list of procedure names to produce an include file and a LIB file. It automatically prepends "crt_" to the function names in the include but all you need to do is do a global replace of "crt_" with another prefix and you solve the name duplication problem.
Initialising the buffer is a good idea, all you need to do is write a single zero as the first byte in the buffer, it does not ned to be zero filled.
Hutch,
Thanks for pointing me to MAKECIMP, I had forgotten all about it. I was able to make a pocrt.inc and pocrt.lib with it and they work. The only drawback is pocrt.dll isn't already on every Windows PC like msvcrt.dll is.
Would you recommend changing the .data? section to .data or add a mov buffer[0],0 to the code?
In the real4$ macro, if I use the VarR8FromR4 API function, I get an assembly error if I pass it anything other than a REAL4, thanks to invoke.
Well it's getting late here, so I'll post what I have done with the sprintf macros.
.586
.MODEL FLAT, stdcall
option casemap:none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\masm32.inc
include c:\masm32\include\msvcrt.inc
include c:\masm32\include\oleaut32.inc
include c:\masm32\macros\macros.asm
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\masm32.lib
includelib c:\masm32\lib\msvcrt.lib
includelib c:\masm32\lib\oleaut32.lib
;; ----------------------------------------------
ubyte$ MACRO ubytevalue:req
; unsigned byte
LOCAL buffer
.data?
buffer BYTE 4 dup(?)
IFNDEF ubfmt
.data
ubfmt BYTE "%hhu", 0
ENDIF
.code
mov buffer[0], 0
mov al, ubytevalue
movzx eax, al
invoke crt_sprintf, ADDR buffer, ADDR ubfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sbyte$ MACRO sbytevalue:req
; signed byte
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF sbfmt
.data
sbfmt BYTE "%hhd", 0
ENDIF
.code
mov buffer[0], 0
mov al, sbytevalue
movsx eax, al
invoke crt_sprintf, ADDR buffer, ADDR sbfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xbyte$ MACRO xbytevalue:req
; unsigned hex byte
LOCAL buffer
.data?
buffer BYTE 4 dup(?)
IFNDEF xbfmt
.data
xbfmt BYTE "%hhX", 0
ENDIF
.code
mov buffer[0], 0
mov al, xbytevalue
movzx eax, al
invoke crt_sprintf, ADDR buffer, ADDR xbfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uword$ MACRO uwordvalue:req
; unsigned word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF uwfmt
.data
uwfmt BYTE "%hu", 0
ENDIF
.code
mov buffer[0], 0
mov ax, uwordvalue
movzx eax, ax
invoke crt_sprintf, ADDR buffer, ADDR uwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sword$ MACRO swordvalue:req
; signed word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF swfmt
.data
swfmt BYTE "%hd", 0
ENDIF
.code
mov buffer[0], 0
mov ax, swordvalue
movsx eax, ax
invoke crt_sprintf, ADDR buffer, ADDR swfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xword$ MACRO xwordvalue:req
; unsigned hex word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF xwfmt
.data
xwfmt BYTE "%hX", 0
ENDIF
.code
mov buffer[0], 0
mov ax, xwordvalue
movzx eax, ax
invoke crt_sprintf, ADDR buffer, ADDR xwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
udword$ MACRO udwordvalue:req
; unsigned dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF udwfmt
.data
udwfmt BYTE "%lu", 0
ENDIF
.code
mov buffer[0], 0
mov eax, udwordvalue
invoke crt_sprintf, ADDR buffer, ADDR udwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sdword$ MACRO sdwordvalue:req
; signed dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF sdwfmt
.data
sdwfmt BYTE "%ld", 0
ENDIF
.code
mov buffer[0], 0
mov eax, sdwordvalue
invoke crt_sprintf, ADDR buffer, ADDR sdwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xdword$ MACRO xdwordvalue:req
; unsigned hex dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF xdwfmt
.data
xdwfmt BYTE "%lX", 0
ENDIF
.code
mov buffer[0], 0
mov eax, xdwordvalue
invoke crt_sprintf, ADDR buffer, ADDR xdwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uqword$ MACRO uqwordvalue:req
; unsigned qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF uqwfmt
.data
uqwfmt BYTE "%I64u", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR uqwfmt, uqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sqword$ MACRO sqwordvalue:req
; signed qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF sqwfmt
.data
sqwfmt BYTE "%I64d", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR sqwfmt, sqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xqword$ MACRO xqwordvalue:req
; unsigned hex qword
LOCAL buffer
.data?
buffer BYTE 20 dup(?)
IFNDEF xqwfmt
.data
xqwfmt BYTE "%I64X", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR xqwfmt, xqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real4$ MACRO r4value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
invoke VarR8FromR4, r4value, ADDR r8value
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real8$ MACRO r8value:req
LOCAL buffer
.data?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real10$ MACRO r10value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
finit
fld REAL10 PTR r10value
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
WaitKey MACRO
LOCAL szCrLf, szPAK
.data
szCrLf BYTE 13, 10, 0
szPAK BYTE 13, 10, "Press any key to continue...", 0
.code
invoke crt_printf, ADDR szPAK
call crt__getch
.if (eax == 0) || (eax == 0E0h)
call crt__getch
.endif
invoke crt_printf, ADDR szCrLf
ENDM
;; ----------------------------------------------
.DATA
ubyMax BYTE 255 ; UCHAR_MAX
ubyMin BYTE 0 ; UCHAR_MIN
sbyMax SBYTE 127 ; CHAR_MAX
sbyMin SBYTE -128 ; CHAR_MIN
uwdMax WORD 65535 ; USHRT_MAX
uwdMin WORD 0 ; USHRT_MIN
swdMax SWORD 32767 ; SHRT_MAX
swdMin SWORD -32768 ; SHRT_MIN
udwMax DWORD 4294967295 ; ULONG MAX
udwMin DWORD 0 ; ULONG_MIN
sdwMax SDWORD 2147483647 ; LONG_MAX
sdwMin SDWORD -2147483648 ; LONG_MIN
uqwMax QWORD 18446744073709551615 ; _UI64_MAX
uqwMin QWORD 0 ; _UI64_MIN
sqwMax QWORD 9223372036854775807 ; _I64_MAX
sqwMin QWORD -9223372036854775808 ; _I64_MIN
r4Max REAL4 3.402823466E+38 ; FLT_MAX
r4Min REAL4 -1.175494351E-38 ; FLT_MIN
r8Max REAL8 1.7976931348623158E+308 ; DBL_MAX
r8Min REAL8 -2.2250738585072014E-308 ; DBL_MIN
r10Max REAL10 1.7976931348623158E+308 ; LDBL_MAX ( = DBL_MAX)
r10Min REAL10 -2.2250738585072014E-308 ; LDBL_MIN ( = DBL_MIN)
.CODE
start:
invoke crt_puts, ubyte$(ubyMax)
invoke crt_puts, ubyte$(ubyMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sbyte$(sbyMax)
invoke crt_puts, sbyte$(sbyMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xbyte$(ubyMax)
invoke crt_puts, xbyte$(ubyMin)
invoke crt_puts, SADD(0)
invoke crt_puts, uword$(uwdMax)
invoke crt_puts, uword$(uwdMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sword$(swdMax)
invoke crt_puts, sword$(swdMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xword$(uwdMax)
invoke crt_puts, xword$(uwdMin)
invoke crt_puts, SADD(0)
invoke crt_puts, udword$(udwMax)
invoke crt_puts, udword$(udwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sdword$(sdwMax)
invoke crt_puts, sdword$(sdwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xdword$(udwMax)
invoke crt_puts, xdword$(udwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, uqword$(uqwMax)
invoke crt_puts, uqword$(uqwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, sqword$(sqwMax)
invoke crt_puts, sqword$(sqwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, xqword$(uqwMax)
invoke crt_puts, xqword$(uqwMin)
invoke crt_puts, SADD(0)
invoke crt_puts, real4$(r4Max)
invoke crt_puts, real4$(r4Min)
invoke crt_puts, SADD(0)
invoke crt_puts, real8$(r8Max)
invoke crt_puts, real8$(r8Min)
invoke crt_puts, SADD(0)
invoke crt_puts, real10$(r10Max)
invoke crt_puts, real10$(r10Min)
WaitKey
invoke ExitProcess, 0
end start
On second thought VarR8FromR4 is not very good because it requires an oddball .inc and .lib (oleaut32). ::)
I'm going to sleep on it.
Greg,
I would be inclined to set the buffer to zero dynamically rather than set the section to initialised data as it is more size efficient.
Hutch,
Regarding initializing the buffer:
Good, that's what I thought too, done.
Regarding the possible buffer overflow if a REAL8 is passed to the real4$ macro:
1) Increase the buffer size to 320.
2) Use VarR8FromR4 for the conversion. If called with invoke it causes an assembly-time error if the wrong data type is passed. It requires including oleaut32.inc/.lib.
real4$ MACRO r4value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
invoke VarR8FromR4, r4value, ADDR r8value
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
3) Force the FPU to load a REAL4. It solves the possible buffer overflow but does not cause an assembly-time error.
real4$ MACRO r4value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
finit
fld REAL4 PTR r4value
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
If someone has a better idea please speak up.
I prefer #2, but it's up to you Hutch.
Greg,
This may be a simple solution and it means the user must get the data type correct.
tst4 MACRO arg
IF op_type(arg) EQ 4 ;; if integer register
echo -------------------------------
echo REAL4 Memory operand expected.
echo Integer register not allowed in
echo this context.
echo -------------------------------
.err
ENDIF
IF SIZEOF arg NE 4 ;; if not 4 bytes
echo -------------------------------
echo data size error, requires REAL4
echo -------------------------------
.err
ENDIF
ENDM
Tested with this code.
tst4 FP4(1234.5678)
Hutch,
I like it. Your experience with MASM is showing, and my lack of experience with MASM is showing. :bg I had never used .ERR before. That method could be used in the other macros, but I'm not sure they need it.
Here are the sprintf conversion macros with my concerns addressed:
;; ----------------------------------------------
tst4 MACRO arg
IF op_type(arg) EQ 4 ;; if integer register
echo -------------------------------
echo REAL4 Memory operand expected.
echo Integer register not allowed in
echo this context.
echo -------------------------------
.err
ENDIF
IF SIZEOF arg NE 4 ;; if not 4 bytes
echo -------------------------------
echo data size error, requires REAL4
echo -------------------------------
.err
ENDIF
ENDM
;; ----------------------------------------------
ubyte$ MACRO ubytevalue:req
; unsigned byte
LOCAL buffer
.data?
buffer BYTE 4 dup(?)
IFNDEF ubfmt
.data
ubfmt BYTE "%hhu", 0
ENDIF
.code
mov buffer[0], 0
mov al, ubytevalue
movzx eax, al
invoke crt_sprintf, ADDR buffer, ADDR ubfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sbyte$ MACRO sbytevalue:req
; signed byte
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF sbfmt
.data
sbfmt BYTE "%hhd", 0
ENDIF
.code
mov buffer[0], 0
mov al, sbytevalue
movsx eax, al
invoke crt_sprintf, ADDR buffer, ADDR sbfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xbyte$ MACRO xbytevalue:req
; unsigned hex byte
LOCAL buffer
.data?
buffer BYTE 4 dup(?)
IFNDEF xbfmt
.data
xbfmt BYTE "%hhX", 0
ENDIF
.code
mov buffer[0], 0
mov al, xbytevalue
movzx eax, al
invoke crt_sprintf, ADDR buffer, ADDR xbfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uword$ MACRO uwordvalue:req
; unsigned word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF uwfmt
.data
uwfmt BYTE "%hu", 0
ENDIF
.code
mov buffer[0], 0
mov ax, uwordvalue
movzx eax, ax
invoke crt_sprintf, ADDR buffer, ADDR uwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sword$ MACRO swordvalue:req
; signed word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF swfmt
.data
swfmt BYTE "%hd", 0
ENDIF
.code
mov buffer[0], 0
mov ax, swordvalue
movsx eax, ax
invoke crt_sprintf, ADDR buffer, ADDR swfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xword$ MACRO xwordvalue:req
; unsigned hex word
LOCAL buffer
.data?
buffer BYTE 8 dup(?)
IFNDEF xwfmt
.data
xwfmt BYTE "%hX", 0
ENDIF
.code
mov buffer[0], 0
mov ax, xwordvalue
movzx eax, ax
invoke crt_sprintf, ADDR buffer, ADDR xwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
udword$ MACRO udwordvalue:req
; unsigned dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF udwfmt
.data
udwfmt BYTE "%lu", 0
ENDIF
.code
mov buffer[0], 0
mov eax, udwordvalue
invoke crt_sprintf, ADDR buffer, ADDR udwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sdword$ MACRO sdwordvalue:req
; signed dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF sdwfmt
.data
sdwfmt BYTE "%ld", 0
ENDIF
.code
mov buffer[0], 0
mov eax, sdwordvalue
invoke crt_sprintf, ADDR buffer, ADDR sdwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xdword$ MACRO xdwordvalue:req
; unsigned hex dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF xdwfmt
.data
xdwfmt BYTE "%lX", 0
ENDIF
.code
mov buffer[0], 0
mov eax, xdwordvalue
invoke crt_sprintf, ADDR buffer, ADDR xdwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uqword$ MACRO uqwordvalue:req
; unsigned qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF uqwfmt
.data
uqwfmt BYTE "%I64u", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR uqwfmt, uqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sqword$ MACRO sqwordvalue:req
; signed qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF sqwfmt
.data
sqwfmt BYTE "%I64d", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR sqwfmt, sqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xqword$ MACRO xqwordvalue:req
; unsigned hex qword
LOCAL buffer
.data?
buffer BYTE 20 dup(?)
IFNDEF xqwfmt
.data
xqwfmt BYTE "%I64X", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR xqwfmt, xqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real4$ MACRO r4value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
tst4 r4value
finit
fld r4value
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real8$ MACRO r8value:req
LOCAL buffer
.data?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real10$ MACRO r10value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
finit
fld r10value
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
Hi
QuoteThis is an unrelated question. Whenever I have tried to use the internal notation within a quoted string of \t or \n I only ever get those characters as literals in the displayed string and not the TAB or CRLF. Am I missing something in how I format the strings.
Not having followed things too closely this may be a foolish comment, but normally to get an escape sequence recognized in a string in C I used to have to write it as "\\t" instead of "\t". Have you tried that?
Howard
Howard,
Sad to say I tried that one but it just displays the \\n in the text. I think Greg is right that the compiler expands the text before its sent to the procedure.
Greg,
Here is some macro code that may be useful as well.
% echo num2str(TYPE(reg))
IF TYPE(reg) NE 4
echo ------------------------
echo 32 bit register required
echo ------------------------
.err
ENDIF
I can get TYPE to recognise 2, 4 and 10 byte registers. It fails on 1 byte regs and does not appear to work correctly on mm or xmm registers.
Hutch,
Just using TYPE would be simpler. It seems to be the same as using just SIZE. What's eluding me is how to determine if a 32-bit variable is a REAL4 or not. I have been reading the MASM manuals and I don't see a way to do it. MASM doesn't seem to differentiate between DWORD and REAL4 or QWORD and REAL8. They seem to be just aliases. I like to use them though for clarity. You could use FXAM but that would only tell you whether a number is a valid floating-point or not, which the bit pattern of many integers would be considered as.
One thing I don't like about the current macro is you can't pass a REAL4 in a 32-bit register, as FLD won't load a CPU register.
What do you think of this, it does the size checking and allows passing a REAL4 in a 32-bit register too.
;; ----------------------------------------------
real4$ MACRO r4value:req
LOCAL buffer, r8value, r4tmp
.data?
r4tmp REAL4 ?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
push r4value
pop r4tmp
finit
fld r4tmp
fstp r8value
fwait
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
I think I have it now. The macros accept both memory and register variables.
sprintf macros
.586
.MODEL FLAT, stdcall
option casemap:none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\masm32.inc
include c:\masm32\include\msvcrt.inc
include c:\masm32\macros\macros.asm
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\masm32.lib
includelib c:\masm32\lib\msvcrt.lib
;; ----------------------------------------------
isregister MACRO tst
IF op_type(tst) EQ 4
EXITM <1>
ELSE
EXITM <0>
ENDIF
ENDM
;; ----------------------------------------------
ubyte$ MACRO ubytevalue:req
; unsigned byte
LOCAL buffer, ubtmp
.data?
ubtmp BYTE ?
buffer BYTE 4 dup(?)
IFNDEF ubfmt
.data
ubfmt BYTE "%hhu", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(ubytevalue)
mov ubtmp, ubytevalue
movzx eax, ubtmp
ELSE
mov al, ubytevalue
movzx eax, al
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR ubfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sbyte$ MACRO sbytevalue:req
; signed byte
LOCAL buffer, sbtmp
.data?
sbtmp SBYTE ?
buffer BYTE 8 dup(?)
IFNDEF sbfmt
.data
sbfmt BYTE "%hhd", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(sbytevalue)
mov sbtmp, sbytevalue
movsx eax, sbtmp
ELSE
mov al, sbytevalue
movsx eax, al
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR sbfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xbyte$ MACRO xbytevalue:req
; unsigned hex byte
LOCAL buffer, xbtmp
.data?
xbtmp BYTE ?
buffer BYTE 4 dup(?)
IFNDEF xbfmt
.data
xbfmt BYTE "%hhX", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(xbytevalue)
mov xbtmp, xbytevalue
movzx eax, xbtmp
ELSE
mov al, xbytevalue
movzx eax, al
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR xbfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uword$ MACRO uwordvalue:req
; unsigned word
LOCAL buffer, uwtmp
.data?
uwtmp WORD ?
buffer BYTE 8 dup(?)
IFNDEF uwfmt
.data
uwfmt BYTE "%hu", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(uwordvalue)
mov uwtmp, uwordvalue
movzx eax, uwtmp
ELSE
mov ax, uwordvalue
movzx eax, ax
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR uwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sword$ MACRO swordvalue:req
; signed word
LOCAL buffer, swtmp
.data?
swtmp SWORD ?
buffer BYTE 8 dup(?)
IFNDEF swfmt
.data
swfmt BYTE "%hd", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(swordvalue)
mov swtmp, swordvalue
movsx eax, swtmp
ELSE
mov ax, swordvalue
movsx eax, ax
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR swfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xword$ MACRO xwordvalue:req
; unsigned hex word
LOCAL buffer, xwtmp
.data?
xwtmp WORD ?
buffer BYTE 8 dup(?)
IFNDEF xwfmt
.data
xwfmt BYTE "%hX", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(xwordvalue)
mov xwtmp, xwordvalue
movzx eax, xwtmp
ELSE
mov ax, xwordvalue
movzx eax, ax
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR xwfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
udword$ MACRO udwordvalue:req
; unsigned dword
LOCAL buffer, udtmp
.data?
udtmp DWORD ?
buffer BYTE 12 dup(?)
IFNDEF udfmt
.data
udfmt BYTE "%lu", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(udwordvalue)
mov udtmp, udwordvalue
mov eax, udtmp
ELSE
mov eax, udwordvalue
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR udfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sdword$ MACRO sdwordvalue:req
; signed dword
LOCAL buffer, sdtmp
.data?
sdtmp SDWORD ?
buffer BYTE 12 dup(?)
IFNDEF sdfmt
.data
sdfmt BYTE "%ld", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(sdwordvalue)
mov sdtmp, sdwordvalue
mov eax, sdtmp
ELSE
mov eax, sdwordvalue
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR sdfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xdword$ MACRO xdwordvalue:req
; unsigned hex dword
LOCAL buffer, xdtmp
.data?
xdtmp DWORD ?
buffer BYTE 12 dup(?)
IFNDEF xdfmt
.data
xdfmt BYTE "%lX", 0
ENDIF
.code
mov buffer[0], 0
IF isregister(xdwordvalue)
mov xdtmp, xdwordvalue
mov eax, xdtmp
ELSE
mov eax, xdwordvalue
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR xdfmt, eax
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
uqword$ MACRO uqwordvalue:req
; unsigned qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF uqwfmt
.data
uqwfmt BYTE "%I64u", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR uqwfmt, uqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
sqword$ MACRO sqwordvalue:req
; signed qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF sqwfmt
.data
sqwfmt BYTE "%I64d", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR sqwfmt, sqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
xqword$ MACRO xqwordvalue:req
; unsigned hex qword
LOCAL buffer
.data?
buffer BYTE 20 dup(?)
IFNDEF xqwfmt
.data
xqwfmt BYTE "%I64X", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR xqwfmt, xqwordvalue
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real4$ MACRO r4value:req
LOCAL buffer, r8value, r4tmp
.data?
r4tmp REAL4 ?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
push r4value
pop r4tmp
finit
fld r4tmp
fstp r8value
fwait
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real8$ MACRO r8value:req
LOCAL buffer
.data?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
real10$ MACRO r10value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
IF isregister(r10value)
fld r10value
ELSE
finit
fld r10value
ENDIF
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
;; ----------------------------------------------
;; ----------------------------------------------
printf MACRO pszFmt, args:VARARG
IFB <args>
IFB <pszFmt>
invoke crt_printf, chr$(13,10)
ELSE
invoke crt_printf, pszFmt
ENDIF
ELSE
invoke crt_printf, pszFmt, args
ENDIF
ENDM
;------------------------------------------------------------
WaitKey MACRO
printf chr$(13, 10, "Press any key to continue...")
call wait_key
printf
ENDM
;------------------------------------------------------------
.DATA
ubyMax BYTE 255 ; UCHAR_MAX
ubyMin BYTE 0 ; UCHAR_MIN
sbyMax SBYTE 127 ; CHAR_MAX
sbyMin SBYTE -128 ; CHAR_MIN
uwdMax WORD 65535 ; USHRT_MAX
uwdMin WORD 0 ; USHRT_MIN
swdMax SWORD 32767 ; SHRT_MAX
swdMin SWORD -32768 ; SHRT_MIN
udwMax DWORD 4294967295 ; ULONG MAX
udwMin DWORD 0 ; ULONG_MIN
sdwMax SDWORD 2147483647 ; LONG_MAX
sdwMin SDWORD -2147483648 ; LONG_MIN
uqwMax QWORD 18446744073709551615 ; _UI64_MAX
uqwMin QWORD 0 ; _UI64_MIN
sqwMax QWORD 9223372036854775807 ; _I64_MAX
sqwMin QWORD -9223372036854775808 ; _I64_MIN
r4Max REAL4 3.402823466E+38 ; FLT_MAX
r4Min REAL4 -1.175494351E-38 ; FLT_MIN
r8Max REAL8 1.7976931348623158E+308 ; DBL_MAX
r8Min REAL8 -2.2250738585072014E-308 ; DBL_MIN
r10Max REAL10 1.7976931348623158E+308 ; LDBL_MAX ( = DBL_MAX)
r10Min REAL10 -2.2250738585072014E-308 ; LDBL_MIN ( = DBL_MIN)
.CODE
start:
printf SADD("Memory variables",10)
printf SADD("================",10,10)
printf SADD("ubyte$(UCHAR_MAX) = %s",10), ubyte$(ubyMax)
printf SADD("ubyte$(UCHAR_MIN) = %s",10), ubyte$(ubyMin)
printf
printf SADD("sbyte$(CHAR_MAX) = %s",10), sbyte$(sbyMax)
printf SADD("sbyte$(CHAR_MIN) = %s",10), sbyte$(sbyMin)
printf
printf SADD("xbyte$(UCHAR_MAX) = %s",10), xbyte$(ubyMax)
printf SADD("xbyte$(UCHAR_MIN) = %s",10), xbyte$(ubyMin)
printf
printf SADD("uword$(USHRT_MAX) = %s",10), uword$(uwdMax)
printf SADD("uword$(USHRT_MIN) = %s",10), uword$(uwdMin)
printf
printf SADD("sword$(SHRT_MAX) = %s",10), sword$(swdMax)
printf SADD("sword$(SHRT_MIN) = %s",10), sword$(swdMin)
printf
printf SADD("xword$(USHRT_MAX) = %s",10), xword$(uwdMax)
printf SADD("xword$(USHRT_MIN) = %s",10), xword$(uwdMin)
printf
printf SADD("udword$(ULONG_MAX) = %s",10), udword$(udwMax)
printf SADD("udword$(ULONG_MIN) = %s",10), udword$(udwMin)
printf
printf SADD("sdword$(LONG_MAX) = %s",10), sdword$(sdwMax)
printf SADD("sdword$(LONG_MIN) = %s",10), sdword$(sdwMin)
printf
printf SADD("xdword$(ULONG_MAX) = %s",10), xdword$(udwMax)
printf SADD("xdword$(ULONG_MIN) = %s",10), xdword$(udwMin)
printf
printf SADD("uqword$(_UI64_MAX) = %s",10), uqword$(uqwMax)
printf SADD("uqword$(_UI64_MIN) = %s",10), uqword$(uqwMin)
printf
printf SADD("sqword$(_I64_MAX) = %s",10), sqword$(sqwMax)
printf SADD("sqword$(_I64_MIN) = %s",10), sqword$(sqwMin)
printf
printf SADD("xqword$(_UI64_MAX) = %s",10), xqword$(uqwMax)
printf SADD("xqword$(_UI64_MIN) = %s",10), xqword$(uqwMin)
printf
printf SADD("real4$(FLT_MAX) = %s",10), real4$(r4Max)
printf SADD("real4$(FLT_MIN) = %s",10), real4$(r4Min)
printf
printf SADD("real8$(DBL_MAX) = %s",10), real8$(r8Max)
printf SADD("real8$(DBL_MIN) = %s",10), real8$(r8Min)
printf
printf SADD("real10$(LDBL_MAX) = %s",10), real10$(r10Max)
printf SADD("real10$(LDBL_MIN) = %s",10), real10$(r10Min)
printf
printf SADD("Register variables",10)
printf SADD("==================",10,10)
mov al, ubyMax
printf SADD("ubyte$(UCHAR_MAX) = %s",10), ubyte$(al)
mov al, ubyMin
printf SADD("ubyte$(UCHAR_MIN) = %s",10), ubyte$(al)
printf
mov al, sbyMax
printf SADD("sbyte$(CHAR_MAX) = %s",10), sbyte$(al)
mov al, sbyMin
printf SADD("sbyte$(CHAR_MIN) = %s",10), sbyte$(al)
printf
mov al, ubyMax
printf SADD("xbyte$(UCHAR_MAX) = %s",10), xbyte$(al)
mov al, ubyMin
printf SADD("xbyte$(UCHAR_MIN) = %s",10), xbyte$(al)
printf
mov ax, uwdMax
printf SADD("uword$(USHRT_MAX) = %s",10), uword$(ax)
mov ax, uwdMin
printf SADD("uword$(USHRT_MIN) = %s",10), uword$(ax)
printf
mov ax, swdMax
printf SADD("sword$(SHRT_MAX) = %s",10), sword$(ax)
mov ax, swdMin
printf SADD("sword$(SHRT_MIN) = %s",10), sword$(ax)
printf
mov ax, uwdMax
printf SADD("xword$(USHRT_MAX) = %s",10), xword$(ax)
mov ax, uwdMin
printf SADD("xword$(USHRT_MIN) = %s",10), xword$(ax)
printf
mov eax, udwMax
printf SADD("udword$(ULONG_MAX) = %s",10), udword$(eax)
mov eax, udwMin
printf SADD("udword$(ULONG_MIN) = %s",10), udword$(eax)
printf
mov eax, sdwMax
printf SADD("sdword$(LONG_MAX) = %s",10), sdword$(eax)
mov eax, sdwMin
printf SADD("sdword$(LONG_MIN) = %s",10), sdword$(eax)
printf
mov eax, udwMax
printf SADD("xdword$(ULONG_MAX) = %s",10), xdword$(eax)
mov eax, udwMin
printf SADD("xdword$(ULONG_MIN) = %s",10), xdword$(eax)
printf
mov esi, OFFSET uqwMax
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("uqword$(_UI64_MAX) = %s",10), uqword$(edx::eax)
mov esi, OFFSET uqwMin
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("uqword$(_UI64_MIN) = %s",10), uqword$(edx::eax)
printf
mov esi, OFFSET sqwMax
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("sqword$(_I64_MAX) = %s",10), sqword$(edx::eax)
mov esi, OFFSET sqwMin
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("sqword$(_I64_MIN) = %s",10), sqword$(edx::eax)
printf
mov esi, OFFSET uqwMax
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("xqword$(_UI64_MAX) = %s",10), xqword$(edx::eax)
mov esi, OFFSET uqwMin
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("xqword$(_UI64_MIN) = %s",10), xqword$(edx::eax)
printf
mov eax, r4Max
printf SADD("real4$(FLT_MAX) = %s",10), real4$(eax)
mov eax, r4Min
printf SADD("real4$(FLT_MIN) = %s",10), real4$(eax)
printf
mov esi, OFFSET r8Max
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("real8$(DBL_MAX) = %s",10), real8$(edx::eax)
mov esi, OFFSET r8Min
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("real8$(DBL_MIN) = %s",10), real8$(edx::eax)
printf
finit
fld r10Max
printf SADD("real10$(LDBL_MAX) = %s",10), real10$(ST(0))
fstp st(0)
fld r10Min
printf SADD("real10$(LDBL_MIN) = %s",10), real10$(ST(0))
fstp st(0)
WaitKey
invoke ExitProcess, 0
end start
Greg,
I found that ml 6.14 was broken on the TYPE return size for a BYTE where ml 7.0 is not so I put this macro together to be reliable on both.
; **********************************
; test if an argument is an integer
; register and return its byte size
; if it is else return zero if it
; is not a register.
; **********************************
iregsize MACRO item
LOCAL rv
rv = 0
for arg,<eax,ebx,ecx,edx,esp,ebp,esi,edi>
IFIDNI <arg>,<item>
rv = 4
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <arg>,<item>
rv = 1
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<ax,bx,cx,dx,sp,bp,si,di>
IFIDNI <arg>,<item>
rv = 2
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
EXITM %rv
ENDM
Hutch,
I am using ML 6.15, TYPE works correctly on byte sized memory variables but it fails on byte sized registers, it's the same as 6.14 I believe. I like the new macro.
I'm pretty happy with the sprintf macros posted above, if you have any suggestions for improvement or concerns, let me know.
Will the new macro automatically handle upper-case registers like 'AL', 'BL', etc., or should we always use lower-case to use anything that uses these macros? Does the case dependency depend on the command line options that apply to variable names? The macro looks nice!
Phil,
IFIDNI is case insensitive so the macro is case insensitive.
Hutch,
I was playing around with your macro, I extended it, and this works. :dance:
;------------------------------------------------------------
; **********************************
; test if an argument is a register
; and return its byte size if it is
; else return zero if it is not a
; register.
; **********************************
iregsize MACRO item
LOCAL rv
rv = 0
for arg,<eax,ebx,ecx,edx,esp,ebp,esi,edi>
IFIDNI <arg>,<item>
rv = 4
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <arg>,<item>
rv = 1
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<ax,bx,cx,dx,sp,bp,si,di>
IFIDNI <arg>,<item>
rv = 2
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<edx::eax,ecx::ebx>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st,st(0),st(1),st(2),st(3),st(4),st(5),st(6),st(7)>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
EXITM %rv
ENDM
Greg,
The mod looks good. I wonder if there will be any problems with alternative syntax on fp, mmx and xmm register names.
LATER :
I tweaked the version to handle both syntaxes, with or without brackets for the index on mm, xmm and st registers. It was starting to get a bit long so I did a length test on the arg and branched on the length which should prevent redundant code execution.
; ************************************
; Return a register size in BYTES or *
; 0 if the arguent is not a register *
; ************************************
regsize MACRO item
LOCAL rv,ln
rv = 0
ln SIZESTR <item>
IF ln EQ 2
goto two
ELSEIF ln EQ 3
goto three
ELSEIF ln EQ 4
goto four
ELSEIF ln EQ 5
goto five
ELSEIF ln EQ 6
goto six
ELSE
goto notreg
ENDIF
:two
for arg,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <arg>,<item>
rv = 1
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<ax,bx,cx,dx,sp,bp,si,di>
IFIDNI <arg>,<item>
rv = 2
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:three
for arg,<eax,ebx,ecx,edx,esp,ebp,esi,edi>
IFIDNI <arg>,<item>
rv = 4
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st0,st1,st2,st3,st4,st5,st6,st7>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:four
for arg,<xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:five
for arg,<mm(0),mm(1),mm(2),mm(3),mm(4),mm(5),mm(6),mm(7)>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st(0),st(1),st(2),st(3),st(4),st(5),st(6),st(7)>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:six
for arg,<xmm(0),xmm(1),xmm(2),xmm(3),xmm(4),xmm(5),xmm(6),xmm(7)>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
:notreg
EXITM %rv
ENDM
Hutch,
Oops. :red It looks like you have to list them all out.
; **********************************
; test if an argument is a register
; and return its byte size if it is
; else return zero if it is not a
; register.
; **********************************
iregsize MACRO item
LOCAL rv
rv = 0
for arg,<eax,ebx,ecx,edx,esp,ebp,esi,edi>
IFIDNI <arg>,<item>
rv = 4
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <arg>,<item>
rv = 1
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<ax,bx,cx,dx,sp,bp,si,di>
IFIDNI <arg>,<item>
rv = 2
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<edx::eax,ecx::ebx>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st(0),st(1),st(2),st(3),st(4),st(5),st(6),st(7),st,st0,st1,st2,st3,st4,st5,st6,st7>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7,mm,mm(0),mm(1),mm(2),mm(3),mm(4),mm(5),mm(6),mm(7)>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7,xmm,xmm(0),xmm(1),xmm(2),xmm(3),xmm(4),xmm(5),xmm(6),xmm(7)>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
EXITM %rv
ENDM
I think I forgot to put the EDX::EAX register pair test but the idea was to try and reduce the amount of macro code so that it did not get too slow. That was the idea of the length test first so you could branch a lot closewr than running them all until you found a match.
Here is the test piece I used.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
; ************************************
; Return a register size in BYTES or *
; 0 if the arguent is not a register *
; ************************************
regsize MACRO item
LOCAL rv,ln
rv = 0
ln SIZESTR <item>
IF ln EQ 2
goto two
ELSEIF ln EQ 3
goto three
ELSEIF ln EQ 4
goto four
ELSEIF ln EQ 5
goto five
ELSEIF ln EQ 6
goto six
ELSE
goto notreg
ENDIF
:two
for arg,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <arg>,<item>
rv = 1
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<ax,bx,cx,dx,sp,bp,si,di>
IFIDNI <arg>,<item>
rv = 2
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:three
for arg,<eax,ebx,ecx,edx,esp,ebp,esi,edi>
IFIDNI <arg>,<item>
rv = 4
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st0,st1,st2,st3,st4,st5,st6,st7>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:four
for arg,<xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:five
for arg,<mm(0),mm(1),mm(2),mm(3),mm(4),mm(5),mm(6),mm(7)>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st(0),st(1),st(2),st(3),st(4),st(5),st(6),st(7)>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:six
for arg,<xmm(0),xmm(1),xmm(2),xmm(3),xmm(4),xmm(5),xmm(6),xmm(7)>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
:notreg
EXITM %rv
ENDM
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
inkey
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
% echo regsize(al)
% echo regsize(ah)
% echo regsize(ax)
% echo regsize(bx)
% echo regsize(eax)
% echo regsize(edi)
% echo regsize(mm0)
% echo regsize(mm(4))
% echo regsize(st0)
% echo regsize(st(5))
% echo regsize(xmm7)
% echo regsize(xmm(3))
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Hutch,
I missed the 'LATER' part. Yes, it was getting pretty large, the length test helps a lot.
MASM macros are amazing, I never did a lot with them before. I'm just starting to realize what all can be done with them. Thanks for prodding me into learning more about them.
I'm getting about sick of these sprintf conversion macros, I'm sure everyone else is too. :wink But I want to get them right.
Here is the latest update, I hope I'm done with these.
.586
.MODEL FLAT, stdcall
option casemap:none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\masm32.inc
include c:\masm32\include\msvcrt.inc
include c:\masm32\macros\macros.asm
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\masm32.lib
includelib c:\masm32\lib\msvcrt.lib
; ************************************
; Return a register size in BYTES or *
; 0 if the arguent is not a register *
; ************************************
regsize MACRO item
LOCAL rv,ln
rv = 0
ln SIZESTR <item>
IF ln EQ 2
goto two
ELSEIF ln EQ 3
goto three
ELSEIF ln EQ 4
goto four
ELSEIF ln EQ 5
goto five
ELSEIF ln EQ 6
goto six
ELSEIF ln EQ 8
goto eight
ELSE
goto notreg
ENDIF
:two
for arg,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <arg>,<item>
rv = 1
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<ax,bx,cx,dx,sp,bp,si,di>
IFIDNI <arg>,<item>
rv = 2
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:three
for arg,<eax,ebx,ecx,edx,esp,ebp,esi,edi>
IFIDNI <arg>,<item>
rv = 4
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st0,st1,st2,st3,st4,st5,st6,st7>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:four
for arg,<xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:five
for arg,<mm(0),mm(1),mm(2),mm(3),mm(4),mm(5),mm(6),mm(7)>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
for arg,<st(0),st(1),st(2),st(3),st(4),st(5),st(6),st(7)>
IFIDNI <arg>,<item>
rv = 10
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
goto notreg
:six
for arg,<xmm(0),xmm(1),xmm(2),xmm(3),xmm(4),xmm(5),xmm(6),xmm(7)>
IFIDNI <arg>,<item>
rv = 16
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
:eight
for arg,<edx::eax,ecx::ebx>
IFIDNI <arg>,<item>
rv = 8
EXITM
ENDIF
ENDM
IF rv NE 0
EXITM %rv
ENDIF
:notreg
EXITM %rv
ENDM
;---------------------------------------------------
issize MACRO var:req, bytes:req
LOCAL rv
rv = regsize(var)
IFE rv ; if not a register use SIZE
IF SIZE var EQ bytes
EXITM <1>
ELSE
EXITM <0>
ENDIF
ELSE ; it's a register
IF rv EQ bytes
EXITM <1>
ELSE
EXITM <0>
ENDIF
ENDIF
ENDM
; ----------------------------------------------
isregister MACRO var:req
IF regsize(var)
EXITM <1>
ELSE
EXITM <0>
ENDIF
ENDM
; ----------------------------------------------
ubyte$ MACRO ubytevalue:req
;; unsigned byte
LOCAL buffer, ubtmp
.data?
ubtmp BYTE ?
buffer BYTE 4 dup(?)
IFNDEF ubfmt
.data
ubfmt BYTE "%hhu", 0
ENDIF
.code
IFE issize(ubytevalue, 1)
echo ----------------------
echo ubyte$ - requires BYTE
echo ----------------------
.ERR
ENDIF
mov buffer[0], 0
IF isregister(ubytevalue)
mov ubtmp, ubytevalue
movzx eax, ubtmp
ELSE
mov al, ubytevalue
movzx eax, al
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR ubfmt, eax
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
sbyte$ MACRO sbytevalue:req
;; signed byte
LOCAL buffer, sbtmp
.data?
sbtmp SBYTE ?
buffer BYTE 8 dup(?)
IFNDEF sbfmt
.data
sbfmt BYTE "%hhd", 0
ENDIF
.code
IFE issize(sbytevalue, 1)
echo -----------------------
echo sbyte$ - requires SBYTE
echo -----------------------
.ERR
ENDIF
mov buffer[0], 0
IF isregister(sbytevalue)
mov sbtmp, sbytevalue
movsx eax, sbtmp
ELSE
mov al, sbytevalue
movsx eax, al
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR sbfmt, eax
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
xbyte$ MACRO xbytevalue:req
;; unsigned hex byte
LOCAL buffer, xbtmp
.data?
xbtmp BYTE ?
buffer BYTE 4 dup(?)
IFNDEF xbfmt
.data
xbfmt BYTE "%hhX", 0
ENDIF
.code
IFE issize(xbytevalue, 1)
echo ----------------------
echo xbyte$ - requires BYTE
echo ----------------------
.ERR
ENDIF
mov buffer[0], 0
IF isregister(xbytevalue)
mov xbtmp, xbytevalue
movzx eax, xbtmp
ELSE
mov al, xbytevalue
movzx eax, al
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR xbfmt, eax
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
uword$ MACRO uwordvalue:req
;; unsigned word
LOCAL buffer, uwtmp
.data?
uwtmp WORD ?
buffer BYTE 8 dup(?)
IFNDEF uwfmt
.data
uwfmt BYTE "%hu", 0
ENDIF
.code
IFE issize(uwordvalue, 2)
echo ----------------------
echo uword$ - requires WORD
echo ----------------------
.ERR
ENDIF
mov buffer[0], 0
IF isregister(uwordvalue)
mov uwtmp, uwordvalue
movzx eax, uwtmp
ELSE
mov ax, uwordvalue
movzx eax, ax
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR uwfmt, eax
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
sword$ MACRO swordvalue:req
;; signed word
LOCAL buffer, swtmp
.data?
swtmp SWORD ?
buffer BYTE 8 dup(?)
IFNDEF swfmt
.data
swfmt BYTE "%hd", 0
ENDIF
.code
IFE issize(swordvalue, 2)
echo -----------------------
echo sword$ - requires SWORD
echo -----------------------
.ERR
ENDIF
mov buffer[0], 0
IF isregister(swordvalue)
mov swtmp, swordvalue
movsx eax, swtmp
ELSE
mov ax, swordvalue
movsx eax, ax
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR swfmt, eax
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
xword$ MACRO xwordvalue:req
;; unsigned hex word
LOCAL buffer, xwtmp
.data?
xwtmp WORD ?
buffer BYTE 8 dup(?)
IFNDEF xwfmt
.data
xwfmt BYTE "%hX", 0
ENDIF
.code
IFE issize(xwordvalue, 2)
echo ----------------------
echo xword$ - requires WORD
echo ----------------------
.ERR
ENDIF
mov buffer[0], 0
IF isregister(xwordvalue)
mov xwtmp, xwordvalue
movzx eax, xwtmp
ELSE
mov ax, xwordvalue
movzx eax, ax
ENDIF
invoke crt_sprintf, ADDR buffer, ADDR xwfmt, eax
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
udword$ MACRO udwordvalue:req
;; unsigned dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF udfmt
.data
udfmt BYTE "%lu", 0
ENDIF
.code
IFE issize(udwordvalue, 4)
echo ------------------------
echo udword$ - requires DWORD
echo ------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR udfmt, udwordvalue
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
sdword$ MACRO sdwordvalue:req
;; signed dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF sdfmt
.data
sdfmt BYTE "%ld", 0
ENDIF
.code
IFE issize(sdwordvalue, 4)
echo -------------------------
echo sdword$ - requires SDWORD
echo -------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR sdfmt, sdwordvalue
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
xdword$ MACRO xdwordvalue:req
;; unsigned hex dword
LOCAL buffer
.data?
buffer BYTE 12 dup(?)
IFNDEF xdfmt
.data
xdfmt BYTE "%lX", 0
ENDIF
.code
IFE issize(xdwordvalue, 4)
echo ------------------------
echo xdword$ - requires DWORD
echo ------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR xdfmt, xdwordvalue
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
uqword$ MACRO uqwordvalue:req
;; unsigned qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF uqwfmt
.data
uqwfmt BYTE "%I64u", 0
ENDIF
.code
IFE issize(uqwordvalue, 8)
echo ------------------------
echo uqword$ - requires QWORD
echo ------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR uqwfmt, uqwordvalue
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
sqword$ MACRO sqwordvalue:req
;; signed qword
LOCAL buffer
.data?
buffer BYTE 24 dup(?)
IFNDEF sqwfmt
.data
sqwfmt BYTE "%I64d", 0
ENDIF
.code
IFE issize(sqwordvalue, 8)
echo ------------------------
echo sqword$ - requires QWORD
echo ------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR sqwfmt, sqwordvalue
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
xqword$ MACRO xqwordvalue:req
;; unsigned hex qword
LOCAL buffer
.data?
buffer BYTE 20 dup(?)
IFNDEF xqwfmt
.data
xqwfmt BYTE "%I64X", 0
ENDIF
.code
IFE issize(xqwordvalue, 8)
echo ------------------------
echo xqword$ - requires QWORD
echo ------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR xqwfmt, xqwordvalue
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
real4$ MACRO r4value:req
LOCAL buffer, r8value, r4tmp
.data?
r4tmp REAL4 ?
r8value REAL8 ?
buffer BYTE 48 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
IFE issize(r4value, 4)
echo ------------------------
echo real4$ - requires REAL4
echo ------------------------
.ERR
ENDIF
IF isregister(r4value)
push r4value
pop r4tmp
finit
fld r4tmp
ELSE
finit
fld r4value
ENDIF
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
real8$ MACRO r8value:req
LOCAL buffer
.data?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
IFE issize(r8value, 8)
echo ------------------------
echo real8$ - requires REAL8
echo ------------------------
.ERR
ENDIF
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
real10$ MACRO r10value:req
LOCAL buffer, r8value
.data?
r8value REAL8 ?
buffer BYTE 320 dup(?)
IFNDEF r8fmt
.data
r8fmt BYTE "%lf", 0
ENDIF
.code
IFE issize(r10value, 10)
echo -------------------------
echo real10$ - requires REAL10
echo -------------------------
.ERR
ENDIF
fld r10value
fstp r8value
fwait
mov buffer[0], 0
invoke crt_sprintf, ADDR buffer, ADDR r8fmt, r8value
EXITM <OFFSET buffer>
ENDM
; ----------------------------------------------
; ----------------------------------------------
printf MACRO pszFmt, args:VARARG
IFB <args>
IFB <pszFmt>
invoke crt_printf, SADD(10)
ELSE
invoke crt_printf, pszFmt
ENDIF
ELSE
invoke crt_printf, pszFmt, args
ENDIF
ENDM
;------------------------------------------------------------
.DATA
ubyMax BYTE 255 ; UCHAR_MAX
ubyMin BYTE 0 ; UCHAR_MIN
sbyMax SBYTE 127 ; CHAR_MAX
sbyMin SBYTE -128 ; CHAR_MIN
uwdMax WORD 65535 ; USHRT_MAX
uwdMin WORD 0 ; USHRT_MIN
swdMax SWORD 32767 ; SHRT_MAX
swdMin SWORD -32768 ; SHRT_MIN
udwMax DWORD 4294967295 ; ULONG MAX
udwMin DWORD 0 ; ULONG_MIN
sdwMax SDWORD 2147483647 ; LONG_MAX
sdwMin SDWORD -2147483648 ; LONG_MIN
uqwMax QWORD 18446744073709551615 ; _UI64_MAX
uqwMin QWORD 0 ; _UI64_MIN
sqwMax QWORD 9223372036854775807 ; _I64_MAX
sqwMin QWORD -9223372036854775808 ; _I64_MIN
r4Max REAL4 3.402823466E+38 ; FLT_MAX
r4Min REAL4 -1.175494351E-38 ; FLT_MIN
r8Max REAL8 1.7976931348623158E+308 ; DBL_MAX
r8Min REAL8 -2.2250738585072014E-308 ; DBL_MIN
r10Max REAL10 1.7976931348623158E+308 ; LDBL_MAX ( = DBL_MAX)
r10Min REAL10 -2.2250738585072014E-308 ; LDBL_MIN ( = DBL_MIN)
.CODE
start:
printf SADD("Memory variables",10)
printf SADD("================",10,10)
printf SADD("ubyte$(UCHAR_MAX) = %s",10), ubyte$(ubyMax)
printf SADD("ubyte$(UCHAR_MIN) = %s",10), ubyte$(ubyMin)
printf
printf SADD("sbyte$(CHAR_MAX) = %s",10), sbyte$(sbyMax)
printf SADD("sbyte$(CHAR_MIN) = %s",10), sbyte$(sbyMin)
printf
printf SADD("xbyte$(UCHAR_MAX) = %s",10), xbyte$(ubyMax)
printf SADD("xbyte$(UCHAR_MIN) = %s",10), xbyte$(ubyMin)
printf
printf SADD("uword$(USHRT_MAX) = %s",10), uword$(uwdMax)
printf SADD("uword$(USHRT_MIN) = %s",10), uword$(uwdMin)
printf
printf SADD("sword$(SHRT_MAX) = %s",10), sword$(swdMax)
printf SADD("sword$(SHRT_MIN) = %s",10), sword$(swdMin)
printf
printf SADD("xword$(USHRT_MAX) = %s",10), xword$(uwdMax)
printf SADD("xword$(USHRT_MIN) = %s",10), xword$(uwdMin)
printf
printf SADD("udword$(ULONG_MAX) = %s",10), udword$(udwMax)
printf SADD("udword$(ULONG_MIN) = %s",10), udword$(udwMin)
printf
printf SADD("sdword$(LONG_MAX) = %s",10), sdword$(sdwMax)
printf SADD("sdword$(LONG_MIN) = %s",10), sdword$(sdwMin)
printf
printf SADD("xdword$(ULONG_MAX) = %s",10), xdword$(udwMax)
printf SADD("xdword$(ULONG_MIN) = %s",10), xdword$(udwMin)
printf
printf SADD("uqword$(_UI64_MAX) = %s",10), uqword$(uqwMax)
printf SADD("uqword$(_UI64_MIN) = %s",10), uqword$(uqwMin)
printf
printf SADD("sqword$(_I64_MAX) = %s",10), sqword$(sqwMax)
printf SADD("sqword$(_I64_MIN) = %s",10), sqword$(sqwMin)
printf
printf SADD("xqword$(_UI64_MAX) = %s",10), xqword$(uqwMax)
printf SADD("xqword$(_UI64_MIN) = %s",10), xqword$(uqwMin)
printf
printf SADD("real4$(FLT_MAX) = %s",10), real4$(r4Max)
printf SADD("real4$(FLT_MIN) = %s",10), real4$(r4Min)
printf
printf SADD("real8$(DBL_MAX) = %s",10), real8$(r8Max)
printf SADD("real8$(DBL_MIN) = %s",10), real8$(r8Min)
printf
printf SADD("real10$(LDBL_MAX) = %s",10), real10$(r10Max)
printf SADD("real10$(LDBL_MIN) = %s",10), real10$(r10Min)
printf
printf SADD("Register variables",10)
printf SADD("==================",10,10)
mov al, ubyMax
printf SADD("ubyte$(UCHAR_MAX) = %s",10), ubyte$(al)
mov al, ubyMin
printf SADD("ubyte$(UCHAR_MIN) = %s",10), ubyte$(al)
printf
mov al, sbyMax
printf SADD("sbyte$(CHAR_MAX) = %s",10), sbyte$(al)
mov al, sbyMin
printf SADD("sbyte$(CHAR_MIN) = %s",10), sbyte$(al)
printf
mov al, ubyMax
printf SADD("xbyte$(UCHAR_MAX) = %s",10), xbyte$(al)
mov al, ubyMin
printf SADD("xbyte$(UCHAR_MIN) = %s",10), xbyte$(al)
printf
mov ax, uwdMax
printf SADD("uword$(USHRT_MAX) = %s",10), uword$(ax)
mov ax, uwdMin
printf SADD("uword$(USHRT_MIN) = %s",10), uword$(ax)
printf
mov ax, swdMax
printf SADD("sword$(SHRT_MAX) = %s",10), sword$(ax)
mov ax, swdMin
printf SADD("sword$(SHRT_MIN) = %s",10), sword$(ax)
printf
mov ax, uwdMax
printf SADD("xword$(USHRT_MAX) = %s",10), xword$(ax)
mov ax, uwdMin
printf SADD("xword$(USHRT_MIN) = %s",10), xword$(ax)
printf
mov eax, udwMax
printf SADD("udword$(ULONG_MAX) = %s",10), udword$(eax)
mov eax, udwMin
printf SADD("udword$(ULONG_MIN) = %s",10), udword$(eax)
printf
mov eax, sdwMax
printf SADD("sdword$(LONG_MAX) = %s",10), sdword$(eax)
mov eax, sdwMin
printf SADD("sdword$(LONG_MIN) = %s",10), sdword$(eax)
printf
mov eax, udwMax
printf SADD("xdword$(ULONG_MAX) = %s",10), xdword$(eax)
mov eax, udwMin
printf SADD("xdword$(ULONG_MIN) = %s",10), xdword$(eax)
printf
mov esi, OFFSET uqwMax
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("uqword$(_UI64_MAX) = %s",10), uqword$(edx::eax)
mov esi, OFFSET uqwMin
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("uqword$(_UI64_MIN) = %s",10), uqword$(edx::eax)
printf
mov esi, OFFSET sqwMax
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("sqword$(_I64_MAX) = %s",10), sqword$(edx::eax)
mov esi, OFFSET sqwMin
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("sqword$(_I64_MIN) = %s",10), sqword$(edx::eax)
printf
mov esi, OFFSET uqwMax
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("xqword$(_UI64_MAX) = %s",10), xqword$(edx::eax)
mov esi, OFFSET uqwMin
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("xqword$(_UI64_MIN) = %s",10), xqword$(edx::eax)
printf
mov eax, r4Max
printf SADD("real4$(FLT_MAX) = %s",10), real4$(eax)
mov eax, r4Min
printf SADD("real4$(FLT_MIN) = %s",10), real4$(eax)
printf
mov esi, OFFSET r8Max
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("real8$(DBL_MAX) = %s",10), real8$(edx::eax)
mov esi, OFFSET r8Min
mov edx, [esi+4]
mov eax, [esi+0]
printf SADD("real8$(DBL_MIN) = %s",10), real8$(edx::eax)
printf
finit
fld r10Max
printf SADD("real10$(LDBL_MAX) = %s",10), real10$(ST(0))
fstp st(0)
fld r10Min
printf SADD("real10$(LDBL_MIN) = %s",10), real10$(ST(0))
fstp st(0)
inkey SADD(10, "Press any key to exit...")
invoke ExitProcess, 0
end start
[attachment deleted by admin]
Wouldn't it be possible to test the register by using INSTR rather than a loop? Though you may lose the case-insensitivity then...
Also, with the really long code pieces, could they be put in zip files rather than code tags. I'm running a huge (1920x1200) screen and they're still annoying, so people with 1024x768 must be really angry at you ;)
zooba,
I would not worry too much about the problem, this work of Greg's will end up in the macros.asm file so it will not irritate anyone there. the "PHUN" part is I have to produce the documentation for them now that they are stable.
zooba,
Sorry, I'll stop posting the long code pieces. Point taken.
Greg,
Thanks for doing this much work, I know it has been time consuming but we have ended up with not only some very usiful and reliable conversions but some very useful macros for writing other more powerful macros. I now have them added to the reference version of macros.asm.
By getting enough preprocessor code up and running, the capacity has something to offer the more experienced programmer who is interested in extending the precision and accuracy of assembler coding in efficient and time effective ways. A reliable body of preprocessor code also makes the lead time from entry to production with programmers learning assembler programming a lot shorter.
Hutch,
You are welcome, I enjoy writing this stuff, and I am pleased you found it good enough to include in the MASM32 macros.
Greg,
I just want to try this idea out on you. I have so far renamed the sscanf macros to get their names shorter as follows. I was interested in the idea of using EXITM <OFFSET retval> so that these macros could be used like functions and make them easier to use. Does this make sense to you ?
a2ub PROTO pStr:PTR BYTE, pUb :PTR BYTE
a2sb PROTO pStr:PTR BYTE, pSb :PTR SBYTE
h2ub PROTO pStr:PTR BYTE, pUb :PTR BYTE
a2uw PROTO pStr:PTR BYTE, pUw :PTR WORD
a2sw PROTO pStr:PTR BYTE, pSw :PTR SWORD
h2uw PROTO pStr:PTR BYTE, pUw :PTR WORD
a2ud PROTO pStr:PTR BYTE, pUdw:PTR DWORD
a2sd PROTO pStr:PTR BYTE, pSdw:PTR SDWORD
h2ud PROTO pStr:PTR BYTE, pUdw:PTR DWORD
a2uq PROTO pStr:PTR BYTE, pUqw:PTR QWORD
a2sq PROTO pStr:PTR BYTE, pSqw:PTR QWORD
h2sq PROTO pStr:PTR BYTE, pUqw:PTR QWORD
a2r4 PROTO pStr:PTR BYTE, pR4 :PTR REAL4
a2r8 PROTO pStr:PTR BYTE, pR8 :PTR REAL8
a2r10 PROTO pStr:PTR BYTE, pR10:PTR REAL10
PS: I like your avatar from the front cover of Aqualung, I have the CD on my workdesk. :bg
Hutch,
Yes, it makes sense. The names were rather long. I converted one of the sscanf procedures to a macro, just to test the concept, but I never posted it, and I never got around to converting the rest. Looks good to me :U
I have fond memories of the time when the 'Aqualung' album came out.
I removed unneeded 'tmp' variables from udword$, sdword$ and xdword$ in the sprintf macros posted above: http://www.masmforum.com/simple/index.php?topic=1851.msg17946#msg17946
Hutch -
Do I understand this thread right--you are putting together a complete msvcrt.dll in masm source code, with all api's and (hopefully) a faster implementation? Does this include a multi-threaded version as well?? If so, there is a lot of speed to be had in the multi-threaded memory heap allocator, as long as you can avoid using locks and critical sections, in favor of lock-free methods. The locks in msvcrt heap calls account for 60-80% of the time it spends (and 20% on memory management). Lock-free methods can eliminate most of that overhead, tripling allocation performance. Many applications often operate at "heap-bound" performance levels, and better allocator performance can directly show up in running client applications.
I have written and use a complete general purpose memory allocator that is 15 times faster than the msvcrt.dll, and plan to extend it, using lock-free methods, coupled with local dynamic per-thread memory pools. It would be cool if your code supported installable heap management, so that I could make my methods available through the standard heap management calls. My code for this is in C++, and likely to stay that way, but it can provide and access any conceivable interface from its own dll.
Also, I cannot seem to find any description of what parts of the msvcrt codebase are finished and which pieces are still wanting. Such a description would make it a lot easier for those of us who would to add to this project, to be able to know what is needed and wanted at any given time.
Codewarp,
In the latest service pack for masm32 there is a utility that creates both an include fle for the MSVCRT dll and a library as well. MSVCRT is a "known" dll so it can be reliably used from win95b upwards and that was its appeal. The function count is about 730 functions. It sounds like you have some good design there but rewriting a DLL the size of MSVCRT is a very large task.
One of our members recently did some benchmarks on different memory allocation strategies and using the low fragmentation heap there were some performance advantages using it but the results were interesting, the ancient GlobalAlloc() was the fastest on large allocations where HeapAlloc() was faster on repeated small allocations. The big loser was OLE string memory tha used to be fast on win9x but was definitely off the pace on later NT based OS versions.
Variable Argument Functions and Argument Promotion in C, and How it Applies to Calling C Run-Time Library Functions from MASMCertain C Run-Time functions, such as 'printf' and 'scanf', will accept a variable number of arguments. Because there is no formal prototype for variable argument functions, 'default argument promotion' applies. For 'unsigned char', 'signed char', 'unsigned short' and 'signed short' arguments, the C compiler promotes the arguments to either 'unsigned int' or 'signed int' before the variable argument function is called. Also, 'float' arguments are promoted to 'double' before the function is called.
Quote from: ANSI C StandardANSI 3.3.2.2: "If the expression that denotes the called function has a type that does not include a prototype, the integral promotions are performed on each argument and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not agree with the number of parameters, the behavior is undefined."
So, in MASM, we must do the argument promotion ourselves, before calling a variable argument C function. BYTE, SBYTE, WORD and SWORD arguments must be zero-extended or sign-extended to either a DWORD or SDWORD. A REAL4 must be converted to a REAL8 before calling a variable argument C function. A REAL10 must be converted to a REAL8 before calling a variable argument C function, this is explained in the following MSDN excerpt.
Quote from: MSDNPrevious 16-bit versions of Microsoft C/C++ and Microsoft Visual C++ supported the long double, 80-bit precision data type. In Win32 programming, however, the long double data type maps to the double, 64-bit precision data type. The Microsoft run-time library provides long double versions of the math functions only for backward compatibility. The long double function prototypes are identical to the prototypes for their double counterparts, except that the long double data type replaces the double data type. The long double versions of these functions should not be used in new code.
We can still use the REAL10 data type in MASM Win32 programming, but when calling a C Run-Time function you must convert the REAL10 to a REAL8 before the call. I have heard the Intel C compiler supports the REAL10 data type but we are using MSVCRT.DLL (Microsoft).
Passing Strings to C Run-Time Library Functions from MASM
If you pass a string that contains escape sequences ( \t \n \r) to a C Run-Time Library function, like printf, from MASM, it doesn't work.
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib msvcrt.lib
.DATA
count DWORD 2
String BYTE "\tThis is a string containing %d escape sequences\n", 0
.CODE
start:
invoke crt_printf, ADDR String, count
invoke ExitProcess, 0
end start
the output is:
\tThis is a string containing 2 escape sequences\n
The C compiler converts the escape sequences to their ASCII value before sending the string to the function. Here is an example.
C program:
#include <stdio.h>
int main(void)
{
int count = 2;
printf("\tThis is a string containing %d escape sequences\n", count);
return 0;
}
Assembly listing of above C program:
; Listing generated by Microsoft (R) Optimizing Compiler Version 13.10.3077
TITLE test.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
INCLUDELIB LIBC
INCLUDELIB OLDNAMES
_DATA SEGMENT
$SG794 DB 09H, 'This is a string containing %d escape sequences', 0aH
DB 00H
_DATA ENDS
PUBLIC _main
EXTRN _printf:NEAR
; Function compile flags: /Odt
_TEXT SEGMENT
_count$ = -4 ; size = 4
_main PROC NEAR
; File c:\documents and settings\gregory lyon\local settings\temp\test.c
; Line 4
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 51 push ecx
; Line 5
00004 c7 45 fc 02 00
00 00 mov DWORD PTR _count$[ebp], 2
; Line 7
0000b 8b 45 fc mov eax, DWORD PTR _count$[ebp]
0000e 50 push eax
0000f 68 00 00 00 00 push OFFSET FLAT:$SG794
00014 e8 00 00 00 00 call _printf
00019 83 c4 08 add esp, 8
; Line 9
0001c 33 c0 xor eax, eax
; Line 10
0001e 8b e5 mov esp, ebp
00020 5d pop ebp
00021 c3 ret 0
_main ENDP
_TEXT ENDS
END
So, in MASM we need to convert the escape sequences to their ASCII values before passing a string to the C Run-Time function. This works.
.586
.MODEL FLAT, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include masm32.inc
include msvcrt.inc
include c:\masm32\macros\macros.asm
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib msvcrt.lib
.DATA
count DWORD 2
String BYTE 9, "This is a string containing %d escape sequences", 10, 0
.CODE
start:
invoke crt_printf, ADDR String, count
invoke ExitProcess, 0
end start
the output is:
This is a string containing 2 escape sequences
Note that the format specifier ( %d ) is passed the same as it is in C.
This test piece may solve the problem with any of the text functions that embed newline and tab characters. This is just a simple test piece to make sure it works but its probably able to be used as a general purpose string macro to give this support to any string display function that requires it.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
cprintf MACRO txt:VARARG
LOCAL buffer,lbuf,rbuf,sln,flag1,tmp
flag1 = 0
buffer equ <>
lbuf equ <>
rbuf equ <>
FORC char,<txt>
IF flag1 NE 0
IFIDN <char>,<n>
buffer CATSTR buffer,<",13,10,">
flag1 = 0
goto lpend
ENDIF
IFIDN <char>,<t>
buffer CATSTR buffer,<",9,">
flag1 = 0
goto lpend
ENDIF
ENDIF
IFIDN <char>,<\>
flag1 = 1
goto lpend
ENDIF
buffer CATSTR buffer,<char>
:lpend
ENDM
buffer CATSTR buffer,<,0,0,0> ;; append trailing zeros
cpos INSTR buffer,<"",> ;; chomp off the first empty quotes & comma
IF cpos EQ 1
buffer SUBSTR buffer,4
ENDIF
:reloop
sln SIZESTR buffer
cpos INSTR buffer,<"",>
IF cpos EQ 0
goto done
ENDIF
lbuf SUBSTR buffer,1,cpos-1
rbuf SUBSTR buffer,cpos+3
buffer equ <>
buffer CATSTR lbuf,rbuf
goto reloop
:done
sln SIZESTR buffer
buffer SUBSTR buffer,1,sln-4
.data
tmp db buffer
.code
print OFFSET tmp
ENDM
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
inkey
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
cprintf "\tThis is a\n\ttest\n\tof embedded tab\n\tand newline characters\n"
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Hutch,
That's cool, I was thinking about writing a macro to do that. You beat me to it. So if a person wants to use C-style escape sequences, they can. :thumbu
One thing I noticed about what the C compiler does is '\n' is replaced only with 10, and not with 13, 10. How about making '\n' = 10, '\r' = 13 in the macro? Not that it makes a great deal of difference, but it would match the C implementation.
Escape Sequences (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccelng/htm/eleme_18.asp)
Hutch,
Did you convert the sscanf conversion procedures I wrote to macros? I got the impression you were doing that, so I never did it. If you didn't, I would be glad to do it.
I was working on a program today where a macro would work better than the procedure, it got me thinking about this.
Greg,
I have been a bit bogged down of late but I thought the sscanf code was a very good candidate for a set of macros that would compliment the other conversions you have already finished. I had a quick play with the naming for the complete set and using something like,
a = ascii
h = hex
fl = float or fp
2 = to
data size
sb = signed byte
ub = unsigned byte
A naming scheme of something like this makes te whole lot far easier to remember
etc ....
I was just a bit worried that the workload was a bit high when you have other stuff to do.
sscanf conversion macros.
Any suggestions for improvement are welcome.
(the test program requires the newest sprintf conversion macros).
[attachment deleted by admin]
Concatenate multiple strings using strcat C Run-Time function.
.586
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
include msvcrt.inc
includelib msvcrt.lib
.CODE
strmulticat PROC C argcount:DWORD, pDest:PTR BYTE, pArgs:VARARG
; concatenates multiple strings
; uses strcat C Run-Time function
xor eax, eax
.WHILE argcount > 0
push eax
mov edx, pArgs[eax]
invoke crt_strcat, pDest, edx
pop eax
dec argcount
add eax, 4
.ENDW
mov eax, pDest
ret
strmulticat ENDP
END
Greg,
Sorry to be a bit slow coming back. I just tested the sscanf mcros and they work fine in your test piece. :U
This is a test piece that I used to determine how to read the _HUGE value from MSVCRT.DLL, and verify exactly what the value is. The _HUGE value is returned in case of error by a number of the floating-point support routines. The macro is supposed to automate the testing of the return value. I avoided using the FPU to do the comparison because doing so (without complex code) would destroy the return value that the function left on the FPU stack. The code clears the sign bit because some of the functions return +/- HUGE_VAL, where the sign of HUGE_VAL matches the sign of the value that cannot be represented.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
dbl2bin PROTO :REAL8,:DWORD
is_huge_val MACRO dbl
push edx
mov eax, _imp___HUGE
mov eax, [eax]
cmp eax, DWORD PTR dbl
mov eax, 0
jne @F
mov eax, _imp___HUGE
mov eax, [eax+4]
mov edx, DWORD PTR dbl+4
and edx, NOT 80000000h ;; clear sign bit
cmp eax, edx
mov eax, 0
jne @F
mov eax, 1
@@:
pop edx
EXITM <eax>
ENDM
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.data
HUGE_VAL REAL8 0.0
binstr db 65 dup(0)
.code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
mov ebx,_imp___HUGE
fld REAL8 PTR [ebx]
fstp HUGE_VAL
invoke crt_printf, chr$("%E",10,"%f",10), HUGE_VAL, HUGE_VAL
invoke dbl2bin, HUGE_VAL, ADDR binstr
print ADDR binstr,13,10
print "seeeeeeeeeeeffffffffffffffffffffffffffffffffffffffffffffffffffff"
print chr$(13,10)
print ustr$(is_huge_val(HUGE_VAL)),13,10
print ustr$(is_huge_val(HUGE_VAL+1)),13,10
mov eax, input(13,10,13,10,"Press enter to exit...")
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
dbl2bin proc uses edi dbl:REAL8, ptrsz:DWORD
mov eax, DWORD PTR dbl+4
mov edi, ptrsz
sub edi, 1
mov ecx, 32
@@:
add edi, 1
xor edx, edx
shl eax, 1 ; next bit -> carry flag
adc edx, '0'
mov [edi], dl
sub ecx, 1
jnz @B
mov eax, DWORD PTR dbl
mov ecx, 32
@@:
add edi, 1
xor edx, edx
shl eax, 1 ; next bit -> carry flag
adc edx, '0'
mov [edi], dl
sub ecx, 1
jnz @B
mov BYTE PTR [ebx+1], 0 ; terminating null
ret
dbl2bin endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Needn't so many macros. Make it easier like this:
printf PROTO C :dword,:vararg
includelib msvcrt.lib
.data
fmt db "%s",0
str db "run",10,0
.code
start:
invoke printf,offset fmt,offset str
invoke ExitProcess,0
end start