News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Suggestion with MSVCRT code.

Started by hutch--, June 06, 2005, 04:59:13 AM

Previous topic - Next topic

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

hutch--

#1
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
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

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


eschew obfuscation

hutch--

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

Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

GregL

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

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


MichaelW

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

eschew obfuscation

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

GregL

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


hutch--

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
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

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.

eschew obfuscation

hutch--

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
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Jibz

hutch--,

What is it with you and idle loops? :green

hutch--

#12
 :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() );
}
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Jibz

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:

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;
                }
            }
        }
    }
}

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php