Non-blocking read of stdin?

前端 未结 3 1224
我在风中等你
我在风中等你 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条回答
  •  Happy的楠姐
    2021-01-22 11:19

    I am afraid that I haven't managed to get this to work as of yet, however someone else might be able to have a go. The ideas was to use asynchronous I/O with the console std input (I assume the idea of your app is to allow people to write directly into the console window, and read the input as it comes).

    I separated off all the API stuff into a module (MAsynchConsole):

    Option Explicit
    
    Private Const GENERIC_READ          As Long = &H80000000
    Private Const GENERIC_WRITE         As Long = &H40000000
    Private Const OPEN_EXISTING         As Long = 3&
    Private Const FILE_FLAG_OVERLAPPED  As Long = &H40000000
    Private Const FILE_SHARE_READ       As Long = &H1
    
    Private Const FILE_FLAG_NO_BUFFERING As Long = &H20000000
    
    Private Type OVERLAPPED
        Internal                    As Long
        InternalHigh                As Long
        OffsetOrPointer             As Long
        OffsetHigh                  As Long
        hEvent                      As Long
    End Type
    
    Private Type OVERLAPPED_ENTRY
        lpCompletionKey             As Long
        lpOverlapped                As Long ' pointer to OVERLAPPED
        Internal                    As Long
        dwNumberOfBytesTransferred  As Long
    End Type
    
    Private Declare Function AllocConsole Lib "kernel32" () As Long
    
    Private Declare Function CancelIo Lib "Kernel32.dll" ( _
        ByVal hFile As Long _
    ) As Long
    
    Private Declare Function CreateFile Lib "Kernel32.dll" Alias "CreateFileW" ( _
        ByVal lpFileName As Long, _
        ByVal dwDesiredAccess As Long, _
        ByVal dwShareModen As Long, _
        ByRef lpSecurityAttributes As Any, _
        ByVal dwCreationDisposition As Long, _
        ByVal dwFlagsAndAttributes As Long, _
        ByVal hTemplateFile As Long _
    ) As Long
    
    Private Declare Function FreeConsole Lib "kernel32" () As Long
    
    Private Declare Function GetStdHandle Lib "kernel32" ( _
        ByVal nStdHandle As Long _
    ) As Long
    
    
    Private Declare Function ReadFile Lib "Kernel32.dll" ( _
        ByVal hFile As Long, _
        ByVal lpBuffer As Long, _
        ByVal nNumberOfBytesToRead As Long, _
        ByRef lpNumberOfBytesRead As Long, _
        ByRef lpOverlapped As OVERLAPPED _
    ) As Long
    
    Private Declare Function ReadFileEx Lib "Kernel32.dll" ( _
        ByVal hFile As Long, _
        ByVal lpBuffer As Long, _
        ByVal nNumberOfBytesToRead As Long, _
        ByRef lpOverlapped As OVERLAPPED, _
        ByVal lpCompletionRoutine As Long _
    ) As Long
    
    Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
    
    Private m_hStdIn                    As Long
    Private m_uOverlapped               As OVERLAPPED
    Private m_sUnicodeBuffer            As String
    
    Private m_oReadCallback             As IReadCallback
    
    Public Sub CloseConsole()
    
        CancelIo m_hStdIn
        Set m_oReadCallback = Nothing
        m_sUnicodeBuffer = vbNullString
        CloseHandle m_hStdIn
    
        FreeConsole
    
    End Sub
    
    Private Sub FileIOCompletionRoutine( _
        ByVal dwErrorCode As Long, _
        ByVal dwNumberOfBytesTransfered As Long, _
        ByRef uOverlapped As OVERLAPPED _
    )
    
        On Error GoTo ErrorHandler
    
        m_oReadCallback.DataRead "FileIOCompletionRoutine"
        m_oReadCallback.DataRead "dwErrorCode = " & CStr(dwErrorCode)
    
        If dwErrorCode Then
            MsgBox "Error = " & CStr(dwErrorCode)
            CloseConsole
            Exit Sub
        End If
    
        m_oReadCallback.DataRead "dwNumberOfBytesTransfered = " & CStr(dwNumberOfBytesTransfered)
    
        m_oReadCallback.DataRead Left$(m_sUnicodeBuffer, dwNumberOfBytesTransfered)
    
    Exit Sub
    
    ErrorHandler:
        '
    End Sub
    
    Public Sub OpenConsoleForInput(ByRef the_oReadCallback As IReadCallback)
    
        Dim sFileName                   As String
    
        On Error GoTo ErrorHandler
    
        Set m_oReadCallback = the_oReadCallback
    
        AllocConsole
    
        'm_hStdIn = GetStdHandle(-10&)
    
        sFileName = "CONIN$"
        'm_hStdIn = CreateFile(StrPtr(sFileName), GENERIC_READ, FILE_SHARE_READ, ByVal 0&, OPEN_EXISTING,  0&, 0&)
        m_hStdIn = CreateFile(StrPtr(sFileName), GENERIC_READ Or GENERIC_WRITE, FILE_SHARE_READ, ByVal 0&, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0&)
    
        m_oReadCallback.DataRead "m_hStdIn = " & CStr(m_hStdIn)
        m_oReadCallback.DataRead "LastError = " & CStr(Err.LastDllError)
    
        m_sUnicodeBuffer = Space$(8192)
    
    Exit Sub
    
    ErrorHandler:
        Err.Raise Err.Number, Err.Source, Err.Description
    End Sub
    
    Public Sub Read()
    
        Dim nRet                            As Long
        Dim nBytesRead                      As Long
    
        On Error GoTo ErrorHandler
    
        m_oReadCallback.DataRead "About to call ReadFileExe"
    
        'm_uOverlapped.OffsetHigh = 0&
        'm_uOverlapped.OffsetOrPointer = 0&
        'nRet = ReadFile(m_hStdIn, StrPtr(m_sUnicodeBuffer), LenB(m_sUnicodeBuffer), nBytesRead, m_uOverlapped)
        nRet = ReadFileEx(m_hStdIn, StrPtr(m_sUnicodeBuffer), LenB(m_sUnicodeBuffer), m_uOverlapped, AddressOf FileIOCompletionRoutine)
    
        m_oReadCallback.DataRead "nRet = " & CStr(nRet)
    
        m_oReadCallback.DataRead "nBytesRead = " & CStr(nBytesRead)
    
        If nRet = 0 Then
            m_oReadCallback.DataRead "Err.LastDllError = " & CStr(Err.LastDllError)
        Else
            m_oReadCallback.DataRead StrConv(Left$(m_sUnicodeBuffer, nBytesRead), vbUnicode)
        End If
    
    Exit Sub
    
    ErrorHandler:
        Err.Raise Err.Number, Err.Source, Err.Description
    End Sub
    

    This relies on an interface (IReadCallback) to communicate with the main GUI.

    Option Explicit
    
    Public Sub DataRead(ByRef out_sData As String)
        '
    End Sub
    

    This is my sample form (FAsynchConsoleTest) - which uses a Timer (Timer) and RichTextBox (txtStdIn):

    Option Explicit
    
    Implements IReadCallback
    
    Private Sub Form_Load()
    
        MAsynchConsole.OpenConsoleForInput Me
    
        Timer.Enabled = True
    
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
    
        MAsynchConsole.CloseConsole
    
    End Sub
    
    Private Sub IReadCallback_DataRead(out_sData As String)
    
        txtStdIn.SelStart = Len(txtStdIn.Text)
        txtStdIn.SelText = vbNewLine & out_sData
    
    End Sub
    
    Private Sub mnuTimerOff_Click()
    
        Timer.Enabled = False
    
    End Sub
    
    Private Sub mnuTimerOn_Click()
    
        Timer.Enabled = True
    
    End Sub
    
    Private Sub Timer_Timer()
    
        MAsynchConsole.Read
    
    End Sub
    

    Unfortunately, whilst CreateFile() using FILE_FLAG_OVERLAPPED should create a file handle that can be used with async I/O, and the handle seems valid, ReadFileEx() returns 0, and GetLastError is 6, which is:

    //
    // MessageId: ERROR_INVALID_HANDLE
    //
    // MessageText:
    //
    // The handle is invalid.
    //
    #define ERROR_INVALID_HANDLE             6L
    

    The console, interestingly, is frozen whilst this all happens.

    Anyone else have any ideas? The docs seem to suggest that if you use CreateFile() with a console device name, the parameter is ignored.

提交回复
热议问题