News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Overlapped or Asynchronous IO of STD_INPUT_HANDLE

Started by peter_asm, November 03, 2013, 11:16:51 PM

Previous topic - Next topic

peter_asm

I have this application which runs multiple threads and one of those threads uses ReadFile () to read console input from STD_INPUT_HANDLE.
What I do is something like.


local buf[BUFSIZ]:BYTE
local dwRead:DWORD

invoke GetStdHandle, STD_INPUT_HANDLE
xchg eax, ebx

wait_for_signal:
  invoke WaitForSingleObject, ebx, INFINITE
  sub eax, WAIT_OBJECT_0

  cmp eax, 0    ; STD_INPUT_HANDLE
  jnz exit_loop
 
  invoke ReadFile, ebx, addr buf, BUFSIZ, addr dwRead, 0
  ; process contents of buf here
  ; .....
  jmp wait_for_signal

exit_loop:


The problem is that ReadFile() blocks and using OVERLAPPED structure doesn't matter.
Even if you open STD_INPUT_HANDLE with CreateFile() specifying FILE_FLAG_OVERLAPPED, windows ignores the flag and opens as normal.

The ReadFile() blocking means I can't signal the thread to terminate in a safe manner and so what I've decided to do is use CloseHandle()
Very crude I know but since the application is ending anyway, it's the only way I can unblock ReadFile() and exit the thread safely.

TerminateThread would be no different to ExitProcess in this case, I need some way to end the thread safely but because of this ReadFile() problem, all there is right now is CloseHandle()

CloseHandle (GetStdHandle(STD_INPUT_HANDLE))

This is really bad, what's worse is that there doesn't appear to be any solution for it until Vista and later.
CancelIo() would work if in the same thread but since ReadFile() blocks on STDIN, there's only CancelIoEx available on Vista if I'm not mistaken.

Of course, CancelIoEx() may have been undocumented on XP but I haven't checked.

Has anyone ever had that problem with ReadFile() blocking STDIN before and if so, how did you solve it?

qWord

Quote from: peter_asm on November 03, 2013, 11:16:51 PMstructure doesn't matter.
Even if you open STD_INPUT_HANDLE with CreateFile() specifying FILE_FLAG_OVERLAPPED, windows ignores the flag and opens as normal.
AFAIK the console does not support asynchronous IO. You might consider to use the low level console IO functions Peek/ReadConsoleInput() and GetNumberOfConsoleInputEvents() to avoid blocking.
MREAL macros - when you need floating point arithmetic while assembling!

japheth

Quote from: peter_asm on November 03, 2013, 11:16:51 PM
Has anyone ever had that problem with ReadFile() blocking STDIN before and if so, how did you solve it?

You may consider to replace WaitForSingleObject() by WaitForMultipleObjects() and supply 2 objects as arguments. The second object will be an "owned" mutex. By releasing the mutex your thread will be unblocked, AFAIK.

dedndave

if you are looking at "normal" keys, you might consider using crt__kbhit and crt__getch
do away with the thread altogether, and put the rest of the program in a round-robin loop   :P

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

InKyb   PROC

;Polled Keyboard Input - DednDave 8, 2010
;
;This function returns a keystroke in EAX if there is one in the buffer.
;If the buffer is empty, the function returns immediately.
;
;If the keyboard buffer is empty, AH = 0, AL = 0, ZF = 1.
;If the stroke is a regular key, AH = 0, AL = key char, ZF = 0.
;If the stroke is an extended key, AH = extended key, AL = E0h, ZF = 0.
;If the stroke is a function key, AH = function key, AL = 0, ZF = 0.
;
;ECX, EDX are not preserved.

        call    crt__kbhit
        or      eax,eax
        jz      InKyb1

        call    crt__getch
        and     eax,0FFh
        jz      InKyb0

        cmp     al,0E0h
        jnz     InKyb1

InKyb0: push    eax
        call    crt__getch
        pop     edx
        shl     eax,8
        or      eax,edx

InKyb1: retn

InKyb   ENDP

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


alternatively, you could just use crt__kbhit to detect keystrokes
use ReadConsoleInput to read individual keys
that would allow you to test the semaphore for thread exit before reading a key
(if no key and no exit semaphore, Sleep,40 and loop back to crt__kbhit)

peter_asm

Quote from: japheth on November 04, 2013, 12:26:24 AM
You may consider to replace WaitForSingleObject() by WaitForMultipleObjects() and supply 2 objects as arguments. The second object will be an "owned" mutex. By releasing the mutex your thread will be unblocked, AFAIK.

I had something like this initially but what i noticed was STD_INPUT_HANDLE can receive all kinds of console events which isn't normal behavior you see on other operating systems that read console input.

For example, if the console window gains or loses focus, WaitForMultipleObjects() or WaitForSingleObject() will return because of FOCUS_EVENT.
PeekConsoleInput() will return one or more INPUT_RECORD structures and you can use FlushConsoleInputBuffer() to ignore these and continue waiting but then when user presses a key or releases it, that signals a KEY_EVENT.

Code (dedndave) Select

alternatively, you could just use crt__kbhit to detect keystrokes
use ReadConsoleInput to read individual keys
that would allow you to test the semaphore for thread exit before reading a key
(if no key and no exit semaphore, Sleep,40 and loop back to crt__kbhit)


This might be worth looking at again, just hoped there'd be simpler way.
I suppose if after using PeekConsoleInput() was somehow able to check what key was pressed or released and it was carriage return, ReadFile() might return immediately then.

Disabling ENABLE_LINE_INPUT with SetConsoleMode() would probably mean having to buffer each character until carriage return and take into account backspaces..

Came across one solution to this in putty / plink.c


static DWORD WINAPI stdin_read_thread(void *param)
{
    struct input_data *idata = (struct input_data *) param;
    HANDLE inhandle;

    inhandle = GetStdHandle(STD_INPUT_HANDLE);

    while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
    &idata->len, NULL) && idata->len > 0) {
SetEvent(idata->event);
WaitForSingleObject(idata->eventback, INFINITE);
    }

    idata->len = 0;
    SetEvent(idata->event);

    return 0;
}


A separate thread handles reading data and triggers event which WaitForMultipleObjects() waits on in another thread.
This seems to work well too but since I'm ending the application anyway, I decided to stick with CloseHandle(STD_INPUT_HANDLE) for now.

dedndave

as qWord suggested, you can use GetNumberOfConsoleInputEvents, then PeekConsoleInput
what you wind up with with those 2 functions is somehwhat similar to crt__kbhit:
1) use GetNumberOfConsoleInputEvents to see how many input records are in the buffer
2) allocate stack space for that many INPUT_RECORD structures
3) use PeekConsoleInput to fill those structures
4) parse through the records to see if any are keyboard events with key down
4a) you'd probably ignore Shift, Ctrl, Alt keys
5) release the stack allocation

after that, you'd know whether there are any key strokes of interest to be read

the problem with any of these methods is that you have to buffer the results and echo to the display
by itself, that doesn't sound too bad - but, you have to support edit keys like arrow and backspace   :(

peter_asm

I wrote something in C just to test out and it works okay.
Sorry this isn't ASM but it was quicker this way ;)

DWORD ReadStdIn (HANDLE hMutex, BYTE buf[], DWORD dwSize)
{
  INPUT_RECORD ir;
  DWORD recCnt, dwLen, evt;
  HANDLE h[2];
  HANDLE hin = GetStdHandle (STD_INPUT_HANDLE);
 
  h[0] = hin;
  h[1] = hMutex;
 
  // read characters until we reach buffer size
  for (dwLen = 0; dwLen < dwSize;) {
 
    // wait for input or until mutex is released
    evt = WaitForMultipleObjects (2, h, FALSE, INFINITE) - WAIT_OBJECT_0;
   
    // if not STD_INPUT_HANDLE, exit
    if (evt != 0) break;
   
    if (ReadConsoleInput (hin, &ir, sizeof (INPUT_RECORD), &recCnt)) {
      if (ir.EventType == KEY_EVENT) {
        KEY_EVENT_RECORD *pKey = (KEY_EVENT_RECORD*)&ir.Event;
       
        if (!pKey->bKeyDown) {
          continue;
        }
       
        if (pKey->wVirtualKeyCode == VK_BACK) {
          dwLen = (dwLen > 0) ? dwLen-1 : dwLen;
          continue;
        }
       
        if (pKey->uChar.AsciiChar != 0) {
          buf[dwLen++] = pKey->uChar.AsciiChar;
        }
        if (pKey->wVirtualKeyCode == VK_RETURN) break;       
      }
    }
  }
  buf[dwLen] = 0;
  return dwLen;
}


It's just a rough idea that would have to be integrated with thread code I have..
Still have to wait on additional events but the idea is to create a mutex with main thread as owner and then should the thread need to terminate, that function would end too once hMutex is released.

jj2007

Quote from: peter_asm on November 03, 2013, 11:16:51 PM
I have this application which runs multiple threads and one of those threads uses ReadFile () to read console input from STD_INPUT_HANDLE.

The input you are reading, is it output from another console app, or is it really a user typing?

dedndave

i was curious if he could open the "CON" device, then use SetCommTimeouts or something like that
i have never tried that   :P

i use that for serial comm, though

i know it says the console doesn't support asynchronous communications
but, it also says it doesn't support UNICODE, then offers about 50 console functions in A or W flavours - lol

peter_asm

Quote from: jj2007 on November 05, 2013, 12:36:38 AM
The input you are reading, is it output from another console app, or is it really a user typing?

It's a console application that reads user input but also has to display information simultaneously, like a terminal.
ReadFile() works fine as a separate thread, but if I need to end for some reason, it's stuck waiting for input...solved with hitting RETURN but I wanted something that ended straight away
without TerminateThread() or just ExitProcess().

It's not really a big deal I suppose.

I thought console input would work with OVERLAPPED structure but apparently not unless it was created with CreateProcess() ... a named pipe would work.
Was thinking there may be a way with SetStdHandle (STD_INPUT_HANDLE, hNamedPipe) and what dedndave suggests might work too.

Doesn't seem to be any simple way to do it..why did Microsoft send events to hStdInput anyway?  :biggrin:

dedndave

i seem to recall Mike (SlugSnack) using pipes for console i/o in the old forum

http://www.masmforum.com/board/index.php?topic=11880.0

if you use the old forum advanced search, "pipe", and username SlugSnack, there are several threads to browse