The MASM Forum

General => The Campus => Topic started by: demondoido on April 09, 2013, 01:56:04 AM

Title: Win32 Console Addition
Post by: demondoido on April 09, 2013, 01:56:04 AM
I want to do a simple addition using numbers provided by the console line, something like this:


.486
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\user32.lib

.data
    msg1 BYTE "Insert a number:"
    msg2 BYTE "The sum is:"
.data?
    oHandle DWORD ?
    iHandle DWORD ?
    bufferLen DWORD ?
    buffer BYTE ?
.code
start:
    invoke GetStdHandle, -11
    mov oHandle, eax
    invoke GetStdHandle, -10
    mov iHandle, eax
    invoke WriteFile, oHandle, addr msg1, lengthof msg1, NULL, NULL
    invoke ReadFile, iHandle, addr buffer, 1, addr bufferLen, NULL
    mov al, 0h
    mov al, buffer
    mov buffer, 0h ; I need some way to clean the buffer, this way doesn't work
    invoke WriteFile, oHandle, addr msg1, lengthof msg1, NULL, NULL
    invoke ReadFile, iHandle, addr buffer, 1, addr bufferLen, NULL
    add al, buffer
    mov buffer, al
    invoke WriteFile, oHandle, addr msg2, lengthof msg2, NULL, NULL
    invoke WriteFile, oHandle, addr buffer, lengthof buffer, NULL, NULL

   invoke ExitProcess, 0
end start


Can someone give me a light?
Something like the principle of this code...

Thanks..
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 02:02:17 AM
well - you need different buffers for the 2 values

this
    mov al, 0h
    mov al, buffer
    mov buffer, 0h

and this
    add al, buffer
    mov buffer, al

make no sense

the value in EAX (AX, AL) is not preserved across API calls

so - you put the first number in buffer1
put the second number in buffer2
then, convert them from ASCII decimal to binary
then, add them together
then, convert the result from binary to ASCII decimal and display it
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 03:19:03 AM
Hmmm... Sorry for my stupidity,  but I have another problem, when I do this:


invoke WriteFile, oHandle, addr msg1, lengthof msg1, NULL, NULL
    invoke ReadFile, iHandle, addr buffer1, 1, addr bufferLen1, NULL
    invoke WriteFile, oHandle, addr msg1, lengthof msg1, NULL, NULL
invoke ReadFile, iHandle, addr buffer2, 1, addr bufferLen2, NULL


The first number is repeated in the second request, then program ends...... I think there's a problem with keyboard buffer, right?  How can I solve this?
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 04:21:27 AM
that problem is caused by using NULL for lpNumberOfBytesWritten
give it a valid pointer to a dword variable, and it should work
QuoteThis parameter can be NULL only when the lpOverlapped parameter is not NULL.

you've done quite well, really   :t
but, let's step back a little and look at some other things
these are not critical problems, but may help you in the future

.486
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\user32.lib

all of that stuff can be replaced with a single line
        include    \masm32\include\masm32rt.inc
that is a plain text file that you can browse with NotePad to see what's inside

    invoke GetStdHandle, -11
    mov oHandle, eax
    invoke GetStdHandle, -10
    mov iHandle, eax

the windows.inc file defines hundreds of windows constants that will make your code easier to read
STD_INPUT_HANDLE                     equ -10
STD_OUTPUT_HANDLE                    equ -11

so, if i were to write it, it might look like this...
        INVOKE  GetStdHandle,STD_INPUT_HANDLE
        mov     hStdInp,eax
        INVOKE  GetStdHandle,STD_OUTPUT_HANDLE
        mov     hStdOut,eax


the "lengthof" and "sizeof" operators are a little different
in the case of bytes, they yield the same thing
but, if i were to get the "lengthof dwValue" of a dword variable, it would yield 1
if i were to get the "sizeof dwValue" of a dword variable, it would yield 4
lengthof gives you the number of elements
sizeof gives you the number of bytes
it makes little difference in this case, but if i were to modify the program for UNICODE, i'd have a bug
the WriteFile function wants the "number of bytes", not the "number of characters"
in that case, "sizeof" would be more appropriate
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 05:02:42 AM
Oh thanks, really, I don't know how to thank you... Precious tips! But still with the same problem, but now the first number is not repeated in the second request... The program only shows the second request and ends, doesn't wait the input!
The code is:


include  \masm32\include\masm32rt.inc

.data
    msg1 BYTE "Insert a number:"
    msg2 BYTE "The sum is:"
.data?
    oHandle DWORD ?
    iHandle DWORD ?
    writeLen1 DWORD ?
    writeLen2 DWORD ?
    bufferLen1 DWORD ?
    bufferLen2 DWORD ?
    buffer1 BYTE ?
    buffer2 BYTE ?
.code
start:
    invoke GetStdHandle, STD_OUTPUT_HANDLE 
    mov oHandle, eax
    invoke GetStdHandle, STD_INPUT_HANDLE 
    mov iHandle, eax
    invoke WriteConsole, oHandle, addr msg1, lengthof msg1, addr writeLen1, NULL
    invoke ReadConsole, iHandle, addr buffer1, 2, addr bufferLen1, NULL
    invoke WriteConsole, oHandle, addr msg1, lengthof msg1, addr writeLen2, NULL
    invoke ReadConsole, iHandle, addr buffer2, 2, addr bufferLen2, NULL

   invoke ExitProcess, 0
end start


Sorry for being annoying, I really want to learn Assembly from the basic to the advanced (maybe even create an OS hohoho  :lol:)
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 05:19:05 AM
ok - that time, you are reading 2 bytes into 1 byte buffers
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 05:24:56 AM
ohh...  :redface: not resolved switch for reading one byte...
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 05:29:24 AM
well - i didn't notice that you were using WriteConsole/ReadConsole
i would typically use WriteFile/ReadFile
give me a minute to play with it....
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 05:36:47 AM
Both API functions are producing the same problem... Doesn't matter if I use WriteFile/ReadFile or WriteConsole/ReadConsole,  in both cases the second Read function is ignored!
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 05:43:57 AM
yah - i understand what's happening - sort of - lol

the buffer is 1 char long
but, when you enter a value, you are entering a char, plus the carriage return   :P

another problem arises because the ReadFile function does not limit the number of characters entered
so - if you have a buffer of 2 chars and enter 10, those extra characters are still in the buffer
the next time you call ReadFile, poof - those extra characters are sitting there, waiting - lol

you can set up the standard input device so that doesn't happen
invoke SetConsoleMode,hStdInp,ENABLE_LINE_INPUT or ENABLE_ECHO_INPUT or ENABLE_PROCESSED_INPUT
once at the beginning of the program

then use ReadFile and WriteFile in preference to ReadConsole and WriteConsole
Title: Re: Win32 Console Addition
Post by: jj2007 on April 09, 2013, 05:50:17 AM
Quote from: dedndave on April 09, 2013, 05:43:57 AM
but, when you enter a value, you are entering a char, plus the carriage return   :P

Yep!

    buffer1 BYTE 100 dup(?)
    buffer2 BYTE 100 dup(?)
.code
start:
    invoke GetStdHandle, STD_OUTPUT_HANDLE 
    mov oHandle, eax
    invoke GetStdHandle, STD_INPUT_HANDLE 
    mov iHandle, eax
    invoke WriteConsole, oHandle, addr msg1, lengthof msg1, addr writeLen1, NULL
    invoke ReadConsole, iHandle, addr buffer1, 100, addr bufferLen1, NULL
    invoke WriteConsole, oHandle, addr msg1, lengthof msg1, addr writeLen2, NULL
    invoke ReadConsole, iHandle, addr buffer2, 100, addr bufferLen2, NULL
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 05:50:45 AM
also - make the buffer n+1 length
in other words, pass the (buffer length-1) to the function
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 05:59:15 AM
i did the above and it still acts strangely   :lol:
let me fix it.....
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 06:12:57 AM
Very strange, with the way of jj2007 works well, but... 100 bytes buffer to do this?
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 06:16:42 AM
yah - you should be able to use a smaller buffer and specify it's length
i must be doing something wrong, here

here is the test program i am using
in this case, i added 3 extra bytes to the buffer and passed a length 3 less

it acts as though the input buffer is not being emptied by ReadFile
;###############################################################################################

        .XCREF
        .NoList
        INCLUDE    \Masm32\Include\Masm32rt.inc
        .List

;###############################################################################################

        .DATA

msg1       BYTE "Insert a number: "
msg2       BYTE "The sum is: "
CrLf       BYTE 13,10

;***********************************************************************************************

        .DATA?

oHandle    DWORD ?
iHandle    DWORD ?
RdWrBytes  DWORD ?
buffer1    BYTE 4 dup(?)
buffer2    BYTE 4 dup(?)

;###############################################################################################

        .CODE

;***********************************************************************************************

_main   PROC

    invoke GetStdHandle, STD_OUTPUT_HANDLE 
    mov oHandle, eax
    invoke GetStdHandle, STD_INPUT_HANDLE 
    mov iHandle, eax
    invoke SetConsoleMode,iHandle,ENABLE_LINE_INPUT or ENABLE_ECHO_INPUT or ENABLE_PROCESSED_INPUT

    invoke WriteFile, oHandle, addr msg1, sizeof msg1, addr RdWrBytes, NULL
    invoke ReadFile, iHandle, addr buffer1, sizeof buffer1-3, addr RdWrBytes, NULL

    invoke WriteFile, oHandle, addr msg1, sizeof msg1, addr RdWrBytes, NULL
    invoke ReadFile, iHandle, addr buffer2, sizeof buffer2-3, addr RdWrBytes, NULL

    invoke WriteFile, oHandle, addr buffer1, sizeof buffer1-3, addr RdWrBytes, NULL
    invoke WriteFile, oHandle, addr CrLf, sizeof CrLf, addr RdWrBytes, NULL
    invoke WriteFile, oHandle, addr buffer2, sizeof buffer2-3, addr RdWrBytes, NULL
    invoke WriteFile, oHandle, addr CrLf, sizeof CrLf, addr RdWrBytes, NULL

    INVOKE  ExitProcess,0

_main   ENDP

;###############################################################################################

        END     _main
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 06:39:19 AM
I'm searching and I can not find any similar case, this is getting weirder........  :icon_confused:
Title: Re: Win32 Console Addition
Post by: qWord on April 09, 2013, 07:08:50 AM
it maybe simpler for you to use the C runtime library (CRT) for console IO, instead of that raw input method.
include \masm32\include\masm32rt.inc
.const
    frmt1 CHAR "%d",0
    sz0 CHAR "calculates C = A+B whereas A, B and C are signed integers",13,10
            CHAR "A = ",0
    sz1 CHAR "B = ",0
    frmt2 CHAR "C = %d",13,10,0
.code
main proc
LOCAL value1:SDWORD,value2:SDWORD
   
    invoke crt_printf,OFFSET sz0
    invoke crt_scanf,OFFSET frmt1,ADDR value1
    invoke crt_printf,OFFSET sz1
    invoke crt_scanf,OFFSET frmt1,ADDR value2
    mov eax,value1
    add eax,value2
    invoke crt_printf,OFFSET frmt2,eax
   
    inkey
    exit
   
main endp
end main

Remarks that the CRT functions are prefixed by "crt_" in the MASM32 package.
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 07:11:35 AM
Yeap, but I want to use direct the Win32 API :S
Title: Re: Win32 Console Addition
Post by: demondoido on April 09, 2013, 08:35:45 AM
I still look for a solution, but it's complicated.   :(
Title: Re: Win32 Console Addition
Post by: qWord on April 09, 2013, 09:39:57 AM
Quote from: demondoido on April 09, 2013, 08:35:45 AM
I still look for a solution, but it's complicated.   :(
You should move your focus on assembler programming and not waste your time by implementing boring console IO by hand.

;__UNICODE__ EQU 1
include \masm32\include\masm32rt.inc
TCHR macro lbl,args:VARARG
    IFDEF __UNICODE__
        UCSTR lbl,args
    ELSE
        lbl CHAR args
    ENDIF
endm

.const
    TCHR szNewLine,13,10,0
.code

;return: SDWORD
tchr2sdword proc uses esi pszBuffer: ptr TCHAR
LOCAL bSigned:BOOL
   
    mov bSigned,FALSE
    mov esi,pszBuffer
    xor ecx,ecx
    xor eax,eax
    movzx edx,TCHAR ptr [esi]
    .if edx == '-'
        mov bSigned,TRUE
        inc ecx
    .elseif edx == '+'
        inc ecx
    .endif
    .while 1
        movzx edx,TCHAR ptr [esi+ecx*TCHAR]
        .break .if edx != 20h && edx != 9
        inc ecx
    .endw
    .while 1
        movzx edx,TCHAR ptr [esi+ecx*TCHAR]
        .break .if !edx || ecx > 10
        imul eax,10
        lea eax,[eax+edx-'0']
        inc ecx
    .endw
    .if bSigned
        neg eax
    .endif
    ret
   
tchr2sdword endp

; return: BOOL
readLine proc pszBuffer: ptr TCHAR,cc:DWORD
LOCAL hStdIn:HANDLE
LOCAL NumberOfCharsRead:DWORD
   
    mov hStdIn,rv(GetStdHandle,STD_INPUT_HANDLE)
    invoke SetConsoleMode,hStdIn,ENABLE_LINE_INPUT or ENABLE_ECHO_INPUT or ENABLE_PROCESSED_INPUT
    .if rv(ReadConsole,hStdIn,pszBuffer,cc,ADDR NumberOfCharsRead,0) && NumberOfCharsRead
        mov edx,pszBuffer
        xor ecx,ecx
        .while ecx < NumberOfCharsRead
            .if TCHAR ptr [edx+ecx*TCHAR] == 13
                mov TCHAR ptr [edx+ecx*TCHAR],0
                .break
            .endif
            inc ecx
        .endw
        mov eax,TRUE
    .else
        xor eax,eax
    .endif
    ret
   
readLine endp

; return: BOOL
printTStr proc pszTStr:ptr TCHAR,bCRLF:BOOL
LOCAL hStdOut:HANDLE
LOCAL NumberOfCharsWrite:DWORD

    mov hStdOut,rv(GetStdHandle,STD_OUTPUT_HANDLE)
    invoke SetConsoleMode,hStdOut,ENABLE_PROCESSED_OUTPUT or ENABLE_WRAP_AT_EOL_OUTPUT
    mov edx,rv(lstrlen,pszTStr)
    invoke WriteConsole,hStdOut,pszTStr,edx,ADDR NumberOfCharsWrite,0
    .if bCRLF
        invoke WriteConsole,hStdOut,ADDR szNewLine,2,ADDR NumberOfCharsWrite,0
    .endif
    ret
   
printTStr endp


main proc
LOCAL value1:SDWORD
LOCAL sz[256]:TCHAR

    .repeat
       
        fn printTStr,"A = ",FALSE
        .break .if !rv(readLine,ADDR sz,LENGTHOF sz)
       
        invoke tchr2sdword,ADDR sz
        mov value1,eax
       
        fn printTStr,"B = ",FALSE
        .break .if !rv(readLine,ADDR sz,LENGTHOF sz)
       
        invoke tchr2sdword,ADDR sz
        add eax,value1
        fn wsprintf,ADDR sz,"C = %d",eax ; WinAPI ;-)
        invoke printTStr,ADDR sz,TRUE
       
        ;inkey
    .until 1
    invoke ExitProcess,0
   
main endp
end main
Title: Re: Win32 Console Addition
Post by: dedndave on April 09, 2013, 11:16:53 AM
i agree - there's no real need to have a perfect console app
the console is buggish, by nature

however, we should be able to limit line input to some specified length
it shouldn't be that difficult - lol

here is what i'm thinking
when we SetConsoleMode, turn off the ENABLE_LINE_INPUT bit
then, we use ReadFile to read individual bytes until
a) the buffer is full
b) carriage return is detected

i'll play with this some more tomorrow
Title: Re: Win32 Console Addition
Post by: dedndave on April 10, 2013, 10:48:12 AM
i had a little time to play today - not as much as i had hoped   :(

we are in the middle of doing repairs on my sister's house, and it rained last night
made for an interesting evening

at any rate.....
this has been an educational little experience - lol

as far as using Write/Read File/Console, we have typically used the File functions in the past
but, if you want to write UNICODE aware routines,
i would have to say that WriteConsole and ReadConsole make the transition a little smoother
they deal with character counts, whereas the File functions deal with byte counts
the masm32 stdin functions use ReadFile for ANSI and ReadConsole for UNICODE
with a little adjustment, it may be best to use ReadConsole and make a single routine for both

the problem concerning line input, where the function does not limit the character count......

this problem occurs because we normally run with the ENABLE_LINE_INPUT mode bit set
that is how we are able to use ENABLE_ECHO_INPUT
we also typically use ENABLE_PROCESSED_INPUT

QuoteENABLE_ECHO_INPUT
Characters read by the ReadFile or ReadConsole function are written to the active screen buffer
as they are read. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.

ENABLE_LINE_INPUT
The ReadFile or ReadConsole function returns only when a carriage return character is read.
If this mode is disabled, the functions return when one or more characters are available.

ENABLE_PROCESSED_INPUT
CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is
being read by ReadFile or ReadConsole, other control keys are processed by the system and
are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is
also enabled, backspace, carriage return, and line feed characters are handled by the system.

it's like a domino effect of caveats - lol

if you look at the masm32 package StdIn routines,
(\Masm32\M32Lib\stdin.asm and \Masm32\M32Lib\stdinW.asm)
you will see that the input mode is set as mentioned above

what i am just now learning is that "the console mode" is actually 2 different "console modes"
you'd think i would have figured that out, long ago   :lol:
actually, i guess i knew it, but didn't realize the impact
and, i am also learning that the mode set for hStdOut affects the behaviour of hStdInp   :redface:

QuoteENABLE_PROCESSED_OUTPUT
Characters written by the WriteFile or WriteConsole function or echoed by the ReadFile or
ReadConsole function are examined for ASCII control sequences and the correct action is
performed. Backspace, tab, bell, carriage return, and line feed characters are processed.

ENABLE_WRAP_AT_EOL_OUTPUT
When writing with WriteFile or WriteConsole or echoing with ReadFile or ReadConsole,
the cursor moves to the beginning of the next row when it reaches the end of the current
row. This causes the rows displayed in the console window to scroll up automatically
when the cursor advances beyond the last row in the window. It also causes the contents
of the console screen buffer to scroll up (discarding the top row of the console screen buffer)
when the cursor advances beyond the last row in the console screen buffer. If this mode is
disabled, the last character in the row is overwritten with any subsequent characters.

more domino caveats
actually, the descriptions are incomplete and a little inaccurate
they do not mention the behaviour of the left and right arrow keys
and, the input buffer ENABLE_INSERT_MODE seems to be enabled even when the bit is cleared
also true of some of the mouse stuff

so, when we read input, we really ought to be controlling the standard output mode, as well
after playing around with the different mode combinations, i have come to the conclusion
that the best way to go is to turn off ENABLE_LINE_INPUT and ENABLE_ECHO_INPUT,
and write your own code to handle the echo and cursor control stuff
this allows you to control the line length according to buffer size (a la DOS buffered input)
a bit more code - but we get more control over the behaviour - not that bad
on the bright side, the input buffer mode doesn't seem to affect the write functions   :biggrin:

tomorrow, i should have a little time to write a set of routines
Title: Re: Win32 Console Addition
Post by: dedndave on April 10, 2013, 07:00:39 PM
here is a UNICODE aware ConOut routine
ConOut  PROTO   :LPVOID,:DWORD

;***********************************************************************************************

ConOut  PROC USES EBX lpBuffer:LPVOID,nChars:DWORD

;UNICODE aware ConOut routine - DednDave, 4-2013

;--------------------------------------------

    LOCAL   dwOrigModeOut         :DWORD
    LOCAL   nNumberOfCharsWritten :DWORD

;--------------------------------------------

    INVOKE  GetStdHandle,STD_OUTPUT_HANDLE
    xchg    eax,ebx
    INVOKE  GetConsoleMode,ebx,addr dwOrigModeOut
    mov     edx,ENABLE_PROCESSED_OUTPUT or ENABLE_WRAP_AT_EOL_OUTPUT
    .if edx!=dwOrigModeOut
        INVOKE  SetConsoleMode,ebx,edx
    .endif
    xor     ecx,ecx
    mov     nNumberOfCharsWritten,ecx
    INVOKE  WriteConsole,ebx,lpBuffer,nChars,addr nNumberOfCharsWritten,ecx
    mov     edx,dwOrigModeOut
    .if edx!=(ENABLE_PROCESSED_OUTPUT or ENABLE_WRAP_AT_EOL_OUTPUT)
        INVOKE  SetConsoleMode,ebx,edx
    .endif
    mov     eax,nNumberOfCharsWritten
    ret

ConOut  ENDP

;***********************************************************************************************


EDIT: modified so it gets standard handle - a reasonably fast API
EDIT: corrected syntax error "or" was "||"
Title: Re: Win32 Console Addition
Post by: Gunther on April 10, 2013, 09:54:25 PM
Good work, Dave. Thank you.  :t

Gunther
Title: Re: Win32 Console Addition
Post by: demondoido on April 10, 2013, 10:01:05 PM
Wow, you're amazing... Perfect!
Title: Re: Win32 Console Addition
Post by: dedndave on April 11, 2013, 12:13:41 AM
thanks Gunther

and thanks Doido   :P
i don't know about "amazing", though - lol

you can use that routine with INVOKE
put the prototype near the beginning of the source file
    INVOKE  ConOut,offset sString,lengthof sString

i also like to use qWord's macro to define ANSI/UNICODE strings
;tchr macro by qWord

tchr    MACRO   lbl,args:VARARG
    IFDEF __UNICODE__
        UCSTR lbl,args
    ELSE
        lbl db args
    ENDIF
        ENDM


then, before the INCLUDE line, you can define the symbol __UNICODE__
with an equate to build a UNICODE version of the program
by commenting it out, you can build the ANSI version

when i get finished with the ConInp routine, i'll post an example
Title: Re: Win32 Console Addition
Post by: Gunther on April 11, 2013, 12:21:58 AM
Thank you Dave. A clean and clear solution.

Gunther
Title: Re: Win32 Console Addition
Post by: qWord on April 11, 2013, 03:37:31 AM
It should be mention that Read/WriteConsole only works with console input/output screen buffers. For pipes and files the function fails (e.g. redirection). The "correct" code is more complicated and that why we should simply use the CRT...
Title: Re: Win32 Console Addition
Post by: dedndave on April 11, 2013, 05:40:48 AM
that may be true for pipes
but, how often do we want a function that simply gets a string from the user ?

i actually prefer using the CRT for keyboard input, but we should be able to program around the API, too - lol
Title: Re: Win32 Console Addition
Post by: Gunther on April 11, 2013, 05:47:23 AM
Dave,

Quote from: dedndave on April 11, 2013, 05:40:48 AM
that may be true for pipes
but, how often do we want a function that simply gets a string from the user ?

i actually prefer using the CRT for keyboard input, but we should be able to program around the API, too - lol

I agree with you. On the other hand, qWord isn't wrong. For the beginners it's not easy to distinguish the essential from the inessential.

Gunther
Title: Re: Win32 Console Addition
Post by: dedndave on April 11, 2013, 06:28:07 AM
 :biggrin:

long ago, i reported some buggish behaviour of the console window
http://www.masmforum.com/board/index.php?topic=11927.msg90572#msg90572 (http://www.masmforum.com/board/index.php?topic=11927.msg90572#msg90572)

using the CRT input functions seems to help those problems a little, but not completely
at one time, i debugged into the CRT getch function - it is a very involved routine - lol
the guys at ms have been working on improving the CRT for a long time
so - it's probably as good as it gets

even so, i am always interested in learning more about the specifics of console behaviour
but, if i want something to work in a half-way predictable manner, i write a GUI app   8)
Title: Re: Win32 Console Addition
Post by: dedndave on April 12, 2013, 11:36:06 PM
wellllll
i have finally finished analyzing the edit keys - lol

along the way, i discovered a little undocumented fact of line edit modes with ReadConsole and ReadFile
these functions only allow a line length of 254 characters, plus the enter key
so - the simple way to use them is to use a buffer length of 256 chars  :t

still, that doesn't allow you to limit the line length to, say, 10 characters or something
Title: Re: Win32 Console Addition
Post by: qWord on April 13, 2013, 12:26:02 AM
Years back I've written the following function, which limits the input by processing the console events retuned by ReadConsoleInput():
;__UNICODE__ EQU 1
include \masm32\include\masm32rt.inc
.686

TCCHR macro lbl,args:VARARG
    IFDEF __UNICODE__
        UCCSTR lbl,args
    ELSE
        ?cstr? lbl,args
    ENDIF
endm

ReadConsoleTimeOut proto psz:ptr TCHAR,cc:DWORD,dwTimeOut:DWORD
IsValidTChar proto TChar:DWORD
TStrCopyN proto pDest:ptr TCHAR,ccDest:DWORD,pszSrc:ptr TCHAR,ccSrc:DWORD
TStrCat proto pBuffer:DWORD,ccBuffer:DWORD,psz1:DWORD,psz2:DWORD

.code

main proc
LOCAL sz[256]:TCHAR
   
    print "enter up to 10 characters: "
    invoke ReadConsoleTimeOut,ADDR sz,11,INFINITE
    print "your input: ",%'<'
    print ADDR sz,%'>'
    print chr$(13,10)
   
    print "enter up to 128 characters: "
    invoke ReadConsoleTimeOut,ADDR sz,129,INFINITE
    print ADDR sz,13,10
    print "your input: ",%'<'
    print ADDR sz,%'>'
    print chr$(13,10)

    inkey
    exit
main endp

ReadConsoleTimeOut proc uses edi esi ebx psz:ptr TCHAR,cc:DWORD,dwTimeOut:DWORD
LOCAL hStdIn:HANDLE
LOCAL hStdOut:HANDLE
LOCAL pBuffer:ptr TCHAR
LOCAL nEvents:DWORD
LOCAL ticks:DWORD
LOCAL csbi:CONSOLE_SCREEN_BUFFER_INFO
LOCAL ir[10]:INPUT_RECORD

    mov eax,psz
    mov TCHAR ptr [eax],0
    .if !cc
        ret
    .endif
   
    mov edx,cc
    lea edx,[edx*TCHAR]
    .if !rvx(pBuffer = GlobalAlloc,GPTR,edx)
        ret
    .endif
    mov hStdIn,rv(GetStdHandle,STD_INPUT_HANDLE)
    mov hStdOut,rv(GetStdHandle,STD_OUTPUT_HANDLE)
    .if dwTimeOut != INFINITE
        mov ticks,rv(GetTickCount)
    .endif

    xor edi,edi
    xor esi,esi
    .while 1
        .break .if rv(WaitForSingleObject,hStdIn,dwTimeOut) == WAIT_FAILED || eax == WAIT_TIMEOUT
       
        .if dwTimeOut != INFINITE
            mov edx,rv(GetTickCount)
            sub edx,ticks
            .break .if edx > dwTimeOut
            sub dwTimeOut,edx
            mov ticks,eax
        .endif
       
        lea ebx,ir
        assume ebx: ptr INPUT_RECORD
        .break .if !rv(ReadConsoleInput,hStdIn,ebx,LENGTHOF ir,&nEvents)
        .while nEvents != 0
            .if [ebx].EventType == KEY_EVENT && [ebx].KeyEvent.bKeyDown
                fn GetConsoleScreenBufferInfo,hStdOut,&csbi
                movzx edx,[ebx].KeyEvent.wVirtualKeyCode
                .if edx == VK_RETURN || edx == VK_ESCAPE
                    jmp _exit
                .elseif edx == VK_BACK
            @@: .if edi > 0
                        .if csbi.dwCursorPosition.x > 0
                            sub csbi.dwCursorPosition.x,1
                        .else
                            .if csbi.dwCursorPosition.y > 0
                                sub csbi.dwCursorPosition.y,1
                                movzx eax,csbi.dwSize.x
                                sub eax,1
                                mov csbi.dwCursorPosition.x,ax
                            .endif
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        dec edi
                        jmp @F
                    .endif
                .elseif edx == VK_DELETE
            @@: .if edi < esi
                        mov edx,psz
                        lea edx,[edx+edi*TCHAR]
                        mov TCHAR ptr [edx],0
                        lea eax,[edx+TCHAR]
                        invoke TStrCat,psz,cc,psz,eax
                        mov ecx,eax
                        sub ecx,edi
                        invoke WriteConsole,hStdOut,edx,ecx,0,0
                        fn WriteConsole,hStdOut," ",1,0,0
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        sub esi,1
                    .endif
                .elseif edx == VK_LEFT
                    .if edi > 0
                        .if csbi.dwCursorPosition.x > 0
                            sub csbi.dwCursorPosition.x,1
                        .else
                            .if csbi.dwCursorPosition.y > 0
                                sub csbi.dwCursorPosition.y,1
                                mov ax,csbi.dwSize.x
                                dec ax
                                mov csbi.dwCursorPosition.x,ax
                            .endif
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        sub edi,1
                    .endif
                .elseif edx == VK_RIGHT
                    .if edi < esi
                        movzx eax,csbi.dwSize.x
                        sub eax,1
                        .if csbi.dwCursorPosition.x >= ax
                            mov csbi.dwCursorPosition.x,0
                            add csbi.dwCursorPosition.y,1
                        .else
                            add csbi.dwCursorPosition.x,1
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        add edi,1
                    .endif
                .else
                    lea edx,[esi+1]
                    movzx ecx,[ebx].KeyEvent.UnicodeChar
                    .if edx < cc && rv(IsValidTChar,ecx)
                        mov edx,psz
                        lea eax,[edx+edi*TCHAR]
                        invoke TStrCopyN,pBuffer,cc,eax,-1
                       
                        movzx ecx,TCHAR ptr [ebx].KeyEvent.UnicodeChar
                        mov WORD ptr [edx+edi*TCHAR],cx
                        mov WORD ptr [edx+edi*TCHAR+TCHAR],0
                        mov esi,rv(TStrCat,psz,cc,psz,pBuffer)
                        sub eax,edi
                        lea edx,[edx+edi*TCHAR]
                        invoke WriteConsole,hStdOut,edx,eax,0,0
                        add edi,1
                       
                        movzx edx,csbi.dwSize.x
                        sub edx,1
                        .if csbi.dwCursorPosition.x >= dx
                            mov csbi.dwCursorPosition.x,0
                            add csbi.dwCursorPosition.y,1
                        .else
                            add csbi.dwCursorPosition.x,1
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                    .endif
                .endif
            .endif
            add ebx,INPUT_RECORD
            dec nEvents
        .endw
    .endw

_exit:
    invoke GlobalFree,pBuffer
    fn WriteConsole,hStdOut,chr$(13,10),2,0,0
   
    mov eax,esi
    ret

ReadConsoleTimeOut endp

; OUT: valid if eax > 0
IsValidTChar proc uses edx ecx TChar:DWORD

    .const
        TCCHR ValidCharT," \t.,:;\x-_#'\q+-*/\\?{}\a\b[]&\p$\r\l|^~´`=@"
    .code
   
    .if rv(IsCharAlphaNumeric,TChar)
        ret
    .endif
    and TChar,0ffffh
    mov edx,OFFSET ValidCharT
    movzx eax,TCHAR ptr [edx]
    .while eax != 0
        lea edx,[edx+TCHAR]
        .break .if eax == TChar
        movzx eax,TCHAR ptr [edx]
    .endw
    ret

IsValidTChar endp

TStrCopyN proc uses ecx edx ebx edi esi pDest:ptr TCHAR,ccDest:DWORD,pszSrc:ptr TCHAR,ccSrc:DWORD
LOCAL cc:DWORD

    mov edx,pDest
    mov ecx,ccDest
    mov eax,pszSrc
    mov cc,0
    .repeat
        sub ecx,1 ; for termination zero
        .break .if ZERO?
        test ccSrc,-1
        .break .if ZERO?
        .repeat
            movzx ebx,TCHAR ptr [eax]
            test ebx,ebx
            .break .if ZERO?
            IFDEF __UNICODE__
                mov TCHAR ptr [edx],bx
            ELSE
                mov TCHAR ptr [edx],bl
            ENDIF
            lea eax,[eax+TCHAR]
            lea edx,[edx+TCHAR]
            add cc,1
            sub ccSrc,1
            .break .if ZERO?
            sub ecx,1
        .until ZERO?
    .until 1
    mov TCHAR ptr [edx],0
    mov eax,cc
    ret
   
TStrCopyN endp

TStrCat proc uses ecx edx ebx edi esi pBuffer:DWORD,ccBuffer:DWORD,psz1:DWORD,psz2:DWORD
   
    mov edi,pBuffer
    mov ecx,ccBuffer
    mov esi,psz1
    mov edx,psz2
    mov eax,1
    mov ccBuffer,0
   
    sub ecx,1 ; for termination-zero
    jz _exit
   
_begin:
    movzx ebx,TCHAR ptr [esi]
    test ebx,ebx
    jz @F
    IFDEF __UNICODE__
        mov TCHAR ptr [edi],bx
    ELSE
        mov TCHAR ptr [edi],bl
    ENDIF
    lea edi,[edi+TCHAR]
    lea esi,[esi+TCHAR]
    inc ccBuffer
    sub ecx,1
    jz _exit
    jmp _begin
@@:
    test eax,eax
    cmovnz eax,ebx
    cmovnz esi,edx
    jnz _begin
_exit:
    mov TCHAR ptr [edi],0
   
    mov eax,ccBuffer
    ret

TStrCat endp
end main

Title: Re: Win32 Console Addition
Post by: dedndave on April 13, 2013, 12:44:12 AM
interesting code, qWord
i have something similar in mind, but i had no intention of providing a timeout - lol

the basic form i am using looks like this...
    .repeat
        .repeat
            INVOKE  ReadConsoleInput,hStdInp,addr ir,1,addr uRecCnt
            movzx   edx,word ptr ir.EventType
        .until (edx==KEY_EVENT) && (edx==ir.KeyEvent.bKeyDown)
        movzx   eax,word ptr ir.KeyEvent.UnicodeChar
        mov     ecx,ir.KeyEvent.dwControlKeyState
        movzx   edx,word ptr ir.KeyEvent.wVirtualKeyCode

;       .if
;       .elseif
;       .elseif                     handle line input and edit keys here
;       .elseif
;       .endif

    .until (ax==VK_RETURN) && !(cx&(LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED or LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED))
Title: Re: Win32 Console Addition
Post by: qWord on April 13, 2013, 01:53:08 AM
Quote from: dedndave on April 13, 2013, 12:44:12 AMbut i had no intention of providing a timeout
removing that few extra lines should be no problem for anyone  ;)
Title: Re: Win32 Console Addition
Post by: dedndave on April 13, 2013, 02:28:48 AM
you know me - i like to write my own stuff   :P
i am not much of a script kiddie

here are some operations i found - couple of them i wasn't aware of

line edit supports these keys:
backspace, tab, enter, insert, delete, home, end, left/right cursor

combinations:
ctrl left/right arrow - moves cursor to first/last character
ctrl home - removes all characters preceeding the cursor
ctrl end - removes all characters from cursor to end of line

i didn't play with all the key combinations - there might be an "undo" somewhere   :P
or - there could be
Title: Re: Win32 Console Addition
Post by: jj2007 on April 13, 2013, 03:13:55 AM
Quote from: dedndave on April 13, 2013, 02:28:48 AM
i didn't play with all the key combinations - there might be an "undo" somewhere   :P
or - there could be

Ctrl Z doesn't work, and SetConsoleCtrlHandler concerns only special keys like Ctrl C and Ctrl Break. If you are really keen on undo, handle Ctrl Break...
Title: Re: Win32 Console Addition
Post by: dedndave on April 13, 2013, 03:33:26 AM
ctrl-z is normally used to generate an EOF (SUB char)

ctrl-c and ctrl-break should exit/abort
i can make that happen, if i want to
but, my thinking was that the guy might have some ctrl handler in place to handle log off or shut down

i doubt there is an undo in the console - lol
but, we could add our own
i was thinking maybe one of the function keys, perhaps with ctrl or alt
Title: Re: Win32 Console Addition
Post by: dedndave on April 13, 2013, 07:53:59 AM
in this first set of routines, i use a ConIn256 routine that assumes a buffer length of 256 TCHAR's
that makes it simple
in the attachment are both ANSI and UNICODE builds

any problem reports appreciated   :P