The MASM Forum

General => The Workshop => Topic started by: jj2007 on January 03, 2013, 09:04:53 AM

Title: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 09:04:53 AM
A while ago we played with console I/O here (http://www.masmforum.com/board/index.php?topic=16920.msg140977#msg140977).

The core code is this, and it works fine to prefill the console buffer:

@@:        dec ebx        ; write the prefill string to the console input buffer
        js @F
        movzx eax, byte ptr [ecx+ebx]        ; read backwards, start with Len-1
        inc edi        ; count the records
        push 0        ; dwControlKeyState
        push ax        ; UnicodeChar
        push ax        ; wVirtualKeyCode
        push 0        ; looks unprofessional but it works better without MapVirtualKey
        push 1        ; bKeyDown
        push KEY_EVENT
        jmp @B
@@:        invoke GetStdHandle, STD_INPUT_HANDLE        ; Google GetStdHandle WriteConsoleInput FILE_TYPE_PIPE
        xchg eax, ebx
        invoke SetLastError, 1
        mov ebx, hConin                ; Conin$ throws no errors for WriteConsoleInputA but no effect...
;        call GetStInfo
;        xchg eax, ebx
        mov edx, esp                ; buffer start
        push edx                ; create a slot for chars written
        invoke WriteConsoleInputA, ebx, edx, edi, esp        ; ml chokes without the A

Except for pipes... no effect. Googling didn't help, it seems most people use STD_INPUT_HANDLE successfully, but in my code it says invalid handle. Using CONIN$ does not throw an error, but it doesn't fill the buffer either. Any ideas?

Thanks, jj
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 09:27:16 AM
maybe the problem is that you only push the keys (bKeyDown=1), but never release them?
Title: Re: WriteConsoleInput
Post by: nidud on January 03, 2013, 09:35:55 AM
deleted
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 09:43:28 AM
Thanks, qWord. However, it seems not important. The code works perfectly in non-pipe mode.

WriteConsoleInputA returns invalid handle when used with GetStdHandle(STD_INPUT_HANDLE), but only in piped mode. The other console functions work just fine.

WriteConsoleInputA returns no error when used with CreateFile(CONIN$), and it works fine in non-pipe mode with that handle (which is different from std input handle). But the console input buffer doesn't get filled in pipe mode.

@nidud: No, the order is correct. hConin is obtained through CreateFile, not shown here. If I comment it out, WriteConsoleInputA uses std input handle and throws invalid handle error. Both work fine in non-pipe mode.

I attach the code and executables. Latest MasmBasic of today (http://masm32.com/board/index.php?topic=94.msg264#msg264) required if you want to assemble it.
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 09:47:11 AM
Are you using named pipes?
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 09:48:43 AM
No, just CreatePipe. The normal case for passing handles to child processes.
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 09:50:09 AM
Just for testing purpose, I would try it with releasing the keys...
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 10:01:53 AM
No change, exactly as before...

Line 37ff of SendStringsOnDemand.asc:
@@:        dec ebx        ; write the prefill string to the console input buffer
        js @F
        movzx eax, byte ptr [ecx+ebx]        ; read backwards, start with Len-1

        inc edi        ; count the records
        push 0        ; dwControlKeyState
        push ax        ; UnicodeChar
        push ax        ; wVirtualKeyCode
        push 0        ; looks unprofessional but it works better without MapVirtualKey
        push 0        ; bKeyUp
        push KEY_EVENT

        inc edi        ; count the records
        push 0        ; dwControlKeyState
        push ax        ; UnicodeChar
        push ax        ; wVirtualKeyCode
        push 0        ; looks unprofessional but it works better without MapVirtualKey
        push 1        ; bKeyDown
        push KEY_EVENT
        jmp @B
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 10:09:37 AM
can you make a short description how I can reproduce the described bug with the two EXEs you've upload.
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 10:22:30 AM
SendStringsOnDemand.exe "standalone" shows
Enter x, Date or Time: >date
(the stuff before is debug info that I forgot to take out - new attachment below has switched that off)

When launched from CmdGUI_full.exe, it shows

D:\Masm32\CmdGUI>SendStringsOnDemand
..
Ready
Enter x, Date or Time: >[NO DATE HERE...]

The attachment above lacked the *.dat files, here is a complete one.
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 10:47:14 AM
did you try it with SetConsoleMode->ENABLE_ECHO_INPUT for the pipe handle?
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 05:16:55 PM
Good idea, I tried in various places but the answer is always "invalid handle"...

        invoke SetHandleInformation, hWriteOut, hflags, hflags         ; we need only hWriteOut
        invoke SetHandleInformation, hReadIn, hflags, hflags         ; and hReadIn
        invoke SetConsoleMode, hWriteOut, ENABLE_ECHO_INPUT
        deb 4, "SW2", eax, $Err$()
        invoke SetConsoleMode, hReadIn, ENABLE_ECHO_INPUT
        deb 4, "SW2", eax, $Err$()
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 09:02:13 PM
Well, I can't compile your source (SendStringsOnDemand) with MASM. Also, when using jWasm, I get an executable that behaviors differently than the EXE you supplied - I get directly an error "invalid handle".
Also, I've seen some "strange" comments in your code:
LOCAL PipeBytes, buffer[8000]:BYTE ; 8102 works, 8104 crashes, slow for small buffers (e.g. 100 bytes) How should one interpret that?
Title: Re: WriteConsoleInput
Post by: sinsi on January 03, 2013, 09:16:41 PM
"push KEY_EVENT" is that pushing a word?

WORD  EventType;
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 09:21:30 PM
Quote from: sinsi on January 03, 2013, 09:16:41 PM
"push KEY_EVENT" is that pushing a word?

WORD  EventType;
the union must be aligned to 4 (size of the greatest member that is not lager than 8 ) so it should be right.
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 09:23:59 PM
Quote from: qWord on January 03, 2013, 09:02:13 PM
Well, I can't compile your source (SendStringsOnDemand) with MASM. Also, when using jWasm, I get an executable that behaviors differently than the EXE you supplied - I get directly an error "invalid handle".

That is odd. The library \Masm32\MasmBasic\MasmBasic.lib should have time stamp 2 Jan 13, 18:33, 68126 bytes. It doesn't work with ML.exe 6.14 (no SSE2), but 6.15 upwards works fine for me.

Sorry to impose MB on you, but re-writing the whole thing in plain Masm32 would be tedious. I am not proficient in C to write a corresponding example, and I haven't found any full example on the web...

It might even help to find an executable written in another language that does prefilling of an Input$() type prompt.

QuoteAlso, I've seen some "strange" comments in your code:
LOCAL PipeBytes, buffer[8000]:BYTE ; 8102 works, 8104 crashes, slow for small buffers (e.g. 100 bytes) How should one interpret that?

A few lines further down:
invoke ReadFile, hPipe, edi, sizeof buffer, addr PipeBytes, NULL

I have played with several values, of course if you take 2 bytes, you need many ReadFile calls, and that slows it down. Somewhere around 8102 for XP it starts crashing, the usual problem with stack & guard pages, so I chose 8000 as a compromise. This is for XP - Win7 seems to allow higher values.

In any case, thanks a lot for your willingness to help, much appreciated :icon14:

@sinsi: yes, that works perfectly in "direct", i.e. non-piped mode.
pushw KEY_EVENT
...
imul edx, edi, 18

does not crash, but the prefill stops working even in direct mode.
Title: Re: WriteConsoleInput
Post by: qWord on January 03, 2013, 09:44:47 PM
I'v download the library this morning. I get this error for all version of MASM
SendStringsOnDemand.asm(134) : error A2046:missing single or double quotation mark in string
cStyle$(2): Macro Called From
  Input$(3): Macro Called From
   Let(1): Macro Called From

the problem is the angle brackets in the literal "Enter x, Date or Time: !>"
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 10:02:44 PM
Sorry, I use JWasm by default and didn't stumble over that one. Here is a workaround for ML 6.15:

                Let esi=Input$(Chr$("Enter x, Date or Time: ", 62),  esi)                ; let's simulate a prompt...
Title: Re: WriteConsoleInput
Post by: japheth on January 03, 2013, 10:55:02 PM

I do not really understand what you are trying to do, but if it's some inter-process communication with "key-strokes" being sent from a parent process to a child process, you should probably forget using pipes and look at DuplicateHandle() instead.
Title: Re: WriteConsoleInput
Post by: jj2007 on January 03, 2013, 11:15:59 PM
Quote from: japheth on January 03, 2013, 10:55:02 PMI do not really understand what you are trying to do, but if it's some inter-process communication with "key-strokes" being sent from a parent process to a child process, you should probably forget using pipes and look at DuplicateHandle() instead.

The CmdGUI app (CmdGUI_full.exe) launches cmd.exe, displays its output in a standard edit control, and sends lines of text to cmd.exe via a pipe. Cursor up/down works like doskey /history. In short, a standard window instead of a console for cmd.exe. Right-click in CmdGUI to see one advantage of having that.

Then there is a console prog (SendStringsOnDemand.exe) that prompts for input, providing a prefilled string:
Enter x, Date or Time: >date
Everything works fine except prefilling via the pipe.

DuplicateHandle assumes that you have source code control over the child process - not a universal solution.
Title: Re: WriteConsoleInput
Post by: japheth on January 04, 2013, 12:55:04 AM
Quote from: jj2007 on January 03, 2013, 11:15:59 PM
DuplicateHandle assumes that you have source code control over the child process - not a universal solution.

Yes, I see. Additionally, according to MS, DuplicateHandle() will only copy console handles if target process and source process are identical.
Title: Re: WriteConsoleInput
Post by: jj2007 on January 04, 2013, 05:17:53 AM
Are you aware of any plain C or C++ solution for the prompt with prefill? I have searched a lot, but all I found is examples in Python, Linux C++ (https://bbs.archlinux.org/viewtopic.php?pid=1092160) and Ruby using the readline library (http://stackoverflow.com/questions/11023713/pre-filled-prompt-in-ruby) plus a hook (code by Casper (http://stackoverflow.com/users/823617/casper)):

require 'rubygems'
require 'rb-readline'

module RbReadline
  def self.prefill_prompt(str)
    @rl_prefill = str
    @rl_startup_hook = :rl_prefill_hook
  end

  def self.rl_prefill_hook
    rl_insert_text @rl_prefill if @rl_prefill
    @rl_startup_hook = nil
  end
end

RbReadline.prefill_prompt("Previous query")
str = Readline.readline("Enter query: ", true)

puts "You entered: #{str}"


See The GNU Readline Library (http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html), 2.2MB to download, but it seems there is no Windows version.

The functionality is not that exotic; most travel agents still have a terminal-like screen where they can edit client data via prefill. Very old technology, it's amazing that they don't switch to Windows.
Title: Re: WriteConsoleInput
Post by: japheth on January 04, 2013, 04:41:23 PM
> Are you aware of any plain C or C++ solution for the prompt with prefill?

Hm, I guess the best solution is some kind of KI ( deutsch: Koud Intschäggtschen ).

A small routine that reads a pipe and writes to console via WriteConsoleInput(). It has to be copied to the "other" address space and run - may be using CreateRemoteThread().
Title: Re: WriteConsoleInput
Post by: jj2007 on January 04, 2013, 05:22:55 PM
'Son cannot speak for himself, so daddy puts the words in his mouth' - interesting idea, thanks Japheth.
Edit: There is one problem, though: In the CmdGUI case, it's the 'son' who knows what to say, i.e. the child process only knows what the prefilled string should contain...

Still, if anybody knows a console proggie that asks for input and offers a prefilled string, let me know. I had hoped to find something among the DOS command, but those which prompt the user do so without offering a string in the readline, it seems...
Title: Re: WriteConsoleInput
Post by: jj2007 on January 06, 2013, 11:54:12 AM
I've put an update into the QikPad thread in the campus (http://masm32.com/board/index.php?topic=1152.msg11969#msg11969). Until now, I have not found a solution for the piped WriteConsoleInput problem.

What I did find, though, is that inkey (Masm32, using crt_getch) and Inkey (MasmBasic, using ReadFile) both behave badly when piped through cmd.exe: the proggies hang and must be killed via Task Manager. Workaround is to launch them with start myproggie. In contrast, Input$() does not hang the child process. However, the underlying ReadFile cannot be used as a substitute for inkey; SetConsoleMode 0 works in standalone mode, i.e. one keypress is enough, but in pipes it requires a Return. Confusing ::)

                invoke SetLastError, 0
                mov ebx, rv(GetStdHandle, STD_INPUT_HANDLE)
                invoke SetConsoleMode, ebx, 0        ; enable return after single key press (works standalone but not piped)
                push ecx        ; create a slot for chars read
                mov edx, esp
                invoke ReadFile, ebx, addr buffer, 1000, edx, 0        ; works fine, doesn't block, but needs Return in pipes
                deb 4, "ret", eax, $Err$()
                pop ecx        ; chars read
Title: Re: WriteConsoleInput
Post by: jj2007 on January 06, 2013, 11:55:57 AM
I've put an update into the QikPad thread in the campus (http://masm32.com/board/index.php?topic=1152.msg11969#msg11969). Until now, I have not found a solution for the piped WriteConsoleInput problem.

What I did find, though, is that inkey (Masm32, using crt_getch) and Inkey (MasmBasic, using ReadFile) both behave badly when piped through cmd.exe: the proggies hang and must be killed via Task Manager. Workaround is to launch them with start myproggie. In contrast, Input$() does not hang the child process. However, the underlying ReadFile cannot be used as a substitute for inkey; SetConsoleMode 0 works in standalone mode, i.e. one keypress is enough, but in pipes it requires a Return. Confusing ::)

                invoke SetLastError, 0
                mov ebx, rv(GetStdHandle, STD_INPUT_HANDLE)
                invoke SetConsoleMode, ebx, 0        ; enable return after single key press (works standalone but not piped)
                push ecx        ; create a slot for chars read
                mov edx, esp
                invoke ReadFile, ebx, addr buffer, 1000, edx, 0        ; works fine, doesn't block, but needs Return in pipes
                deb 4, "ret", eax, $Err$()
                pop ecx        ; chars read