Non-blocking read of stdin?

前端 未结 3 1186
我在风中等你
我在风中等你 2021-01-22 10:45

I need to have my form-based application check stdin periodically for input, but still perform other processing. Scripting.TextStream.Read() and the ReadFile() API are blocking,

3条回答
  •  栀梦
    栀梦 (楼主)
    2021-01-22 11:11

    wqw's answer doesn't work for a form-based application, but the prototypes given there for Peek/ReadConsoleInput allow for one that does:

    Private Declare Function AllocConsole Lib "kernel32" () As Long
    Private Declare Function FreeConsole Lib "kernel32" () As Long
    Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
    Private Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long
    Private Declare Function PeekConsoleInput Lib "kernel32" Alias "PeekConsoleInputA" (ByVal hConsoleInput As Long, lpBuffer As Any, ByVal nLength As Long, lpNumberOfEventsRead As Long) As Long
    Private Declare Function ReadConsoleInput Lib "kernel32" Alias "ReadConsoleInputA" (ByVal hConsoleInput As Long, lpBuffer As Any, ByVal nLength As Long, lpNumberOfEventsRead As Long) As Long
    Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lpOverlapped As Long) As Long
    Private Declare Function SetConsoleMode Lib "kernel32" (ByVal hConsoleInput As Long, dwMode As Long) As Long
    
    Private Const STD_INPUT_HANDLE As Long = -10& ' GetStdHandle()
    
    Private Const KEY_EVENT As Long = 1 ' PeekConsoleInput()
    
    Private Const ENABLE_PROCESSED_INPUT As Long = &H1 ' SetConsoleMode()
    Private Const ENABLE_ECHO_INPUT As Long = &H4
    
    Dim hStdIn As Long
    
    Private Sub Form_Load()
    
        AllocConsole
    
        hStdIn = GetStdHandle(STD_INPUT_HANDLE)
        SetConsoleMode hStdIn, ENABLE_PROCESSED_INPUT ' Or ENABLE_ECHO_INPUT ' uncomment to see the characters typed (for debugging)
    
        Timer1.Enabled = True
    
        Exit Sub
    
    End Sub
    
    Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    
        CloseHandle hStdIn
        FreeConsole
    
    End Sub
    
    Private Sub Timer1_Timer()
    
        Dim bytesRead As Long
        Dim buffer As String
        Dim baBuffer(0 To 512) As Byte
        Dim lEvents As Long
    
        PeekConsoleInput hStdIn, baBuffer(0), 1, lEvents
        If lEvents > 0 Then
            If baBuffer(0) = KEY_EVENT And baBuffer(4) <> 0 Then ' baBuffer(4) = INPUT_RECORD.bKeyDown
                buffer = Space$(1)
                Call ReadFile(hStdIn, ByVal buffer, Len(buffer), bytesRead, 0)
    
                ' buffer now contains one byte read from console
                ' Statements to process go here.
    
            Else
                Call ReadConsoleInput(hStdIn, baBuffer(0), 1, lEvents)
            End If
        End If
    End Sub
    

    PeekNamedPipe, GetConsoleMode and PeekConsoleInput will all return zero if your app isn't a true VB6 console app (though all that may be required is linking with the console subsystem, e.g., "C:\Program Files\Microsoft Visual Studio\vb98\LINK.EXE" /EDIT /SUBSYSTEM:CONSOLE MyApp.exe, I haven't tested it that far). They still work, however, at least Peek... does.

    It is key that only one byte is read on each pass, as reading what is in baBuffer is problematic past the first record (INPUT_RECORD structure), but one byte at a time non-blocking is better than none at all. For me, Timer1 is set at 100 ms, but a better setting might be 55 ms, the events time slice.

    Also key is that ReadConsoleInput is non-blocking if there is an event present on stdin, not just a key to be read. Using it when the recognized event isn't a key, effectively clears the event, allowing the application to proceed. It is possible to use this to read the bytes from the buffer without using ReadFile at all:

    PeekConsoleInput hStdIn, baBuffer(0), 1, lEvents
    If lEvents > 0 Then
        Call ReadConsoleInput(hStdIn, baBuffer(0), 1, lEvents)
        If baBuffer(0) = KEY_EVENT And baBuffer(4) <> 0 Then
            ' Chr(baBuffer(14)) now produces the character typed...
    

    This hasn't been tested for reading true human input, except in the simplest debugging during construction, but it does work and should allow most VB6 form-based apps to effectively use a console. Thank you wqw!

提交回复
热议问题