You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用WriteConsoleInput向控制台程序发文本失败求助(Wine环境)

解决Wine下WriteConsoleInput无法向控制台程序发送输入的问题

嘿,我看了你的代码,发现核心问题出在对WriteConsoleInput函数的理解上——你可能误以为它像WriteConsole那样直接传字符串就行,但实际上这个函数的参数要求完全不同!这也是为什么你的代码在Windows和Wine下都没法正常工作的原因。

1. 核心错误:WriteConsoleInput的参数类型完全错误

WriteConsoleInput不是用来直接写入字符串的,它的作用是模拟控制台输入事件(比如键盘按键按下、释放,鼠标操作等)。所以第二个参数lpBuffer需要传入的是INPUT_RECORD结构体的数组,而不是字符串或char数组。你直接传char数组,系统无法解析成有效的输入事件,自然不会有任何效果。

2. 修正结构体和函数声明

首先,你需要在VB.NET中正确声明Windows API所需的结构体和函数,保证内存布局和参数类型与系统要求一致:

Imports System.Runtime.InteropServices

' 控制台输入记录类型枚举
Public Enum INPUT_RECORD_TYPE As UShort
    KEY_EVENT = &H1
    MOUSE_EVENT = &H2
    WINDOW_BUFFER_SIZE_EVENT = &H4
    MENU_EVENT = &H8
    FOCUS_EVENT = &H10
End Enum

' 键盘事件结构体
<StructLayout(LayoutKind.Sequential)>
Public Structure KEY_EVENT_RECORD
    Public bKeyDown As Boolean
    Public wRepeatCount As UShort
    Public wVirtualKeyCode As UShort
    Public wVirtualScanCode As UShort
    Public UnicodeChar As Char
    Public dwControlKeyState As Integer
End Structure

' 控制台输入记录结构体
<StructLayout(LayoutKind.Explicit)>
Public Structure INPUT_RECORD
    <FieldOffset(0)> Public EventType As INPUT_RECORD_TYPE
    <FieldOffset(4)> Public KeyEvent As KEY_EVENT_RECORD
    ' 其他事件类型可省略,我们只用到键盘事件
End Structure

' 修正后的API声明
<DllImport("kernel32.dll", SetLastError:=True)>
Public Shared Function AttachConsole(ByVal dwProcessId As Integer) As Boolean
End Function

<DllImport("kernel32.dll", SetLastError:=True)>
Public Shared Function GetStdHandle(ByVal nStdHandle As Integer) As IntPtr
End Function

<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
Public Shared Function WriteConsoleInputW(
    ByVal hConsoleInput As IntPtr,
    ByRef lpBuffer As INPUT_RECORD,
    ByVal nNumberOfRecordsToWrite As Integer,
    ByRef lpNumberOfRecordsWritten As Integer
) As Boolean
End Function

<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Ansi)>
Public Shared Function SetConsoleTitleA(ByVal lpConsoleTitle As String) As Boolean
End Function

<DllImport("kernel32.dll", SetLastError:=True)>
Public Shared Function FreeConsole() As Boolean
End Function

' 标准句柄常量
Private Const STD_INPUT_HANDLE As Integer = -10
Private Const INVALID_HANDLE_VALUE As IntPtr = New IntPtr(-1)

3. 修正Button_Click逻辑

接下来,修改你的Button1_Click方法,构造正确的键盘输入事件来模拟字符串输入:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim titleStr As String = "Test"
    Dim textToWrite As String = "test"
    Dim hConsoleInput As IntPtr
    Dim recordsWritten As Integer = 0
    Dim success As Boolean

    ' 获取目标CMD的PID,先检查是否有效
    Dim targetPid As Integer = ReturnCMDProcessID()
    If targetPid = 0 Then
        MessageBox.Show("CMD off")
        Exit Sub
    End If

    ' 附加到目标控制台
    If Not AttachConsole(targetPid) Then
        MessageBox.Show($"AttachConsole失败,错误码: {Marshal.GetLastWin32Error()}")
        Exit Sub
    End If

    Try
        ' 获取标准输入句柄
        hConsoleInput = GetStdHandle(STD_INPUT_HANDLE)
        If hConsoleInput = INVALID_HANDLE_VALUE Then
            MessageBox.Show($"GetStdHandle失败,错误码: {Marshal.GetLastWin32Error()}")
            Exit Sub
        End If

        ' 构造输入记录数组:每个字符需要按下和释放两个事件
        Dim inputRecords As New List(Of INPUT_RECORD)()
        For Each c As Char In textToWrite
            ' 按下事件
            Dim keyDownRecord As New INPUT_RECORD()
            keyDownRecord.EventType = INPUT_RECORD_TYPE.KEY_EVENT
            keyDownRecord.KeyEvent.bKeyDown = True
            keyDownRecord.KeyEvent.wRepeatCount = 1
            keyDownRecord.KeyEvent.UnicodeChar = c
            keyDownRecord.KeyEvent.wVirtualKeyCode = CType(Char.ToUpper(c), UShort)
            keyDownRecord.KeyEvent.dwControlKeyState = 0
            inputRecords.Add(keyDownRecord)

            ' 释放事件
            Dim keyUpRecord As New INPUT_RECORD()
            keyUpRecord.EventType = INPUT_RECORD_TYPE.KEY_EVENT
            keyUpRecord.KeyEvent.bKeyDown = False
            keyUpRecord.KeyEvent.wRepeatCount = 1
            keyUpRecord.KeyEvent.UnicodeChar = c
            keyUpRecord.KeyEvent.wVirtualKeyCode = CType(Char.ToUpper(c), UShort)
            keyUpRecord.KeyEvent.dwControlKeyState = 0
            inputRecords.Add(keyUpRecord)
        Next

        ' 可选:添加回车事件,让CMD执行输入的命令
        Dim enterDown As New INPUT_RECORD()
        enterDown.EventType = INPUT_RECORD_TYPE.KEY_EVENT
        enterDown.KeyEvent.bKeyDown = True
        enterDown.KeyEvent.wVirtualKeyCode = &HD ' VK_RETURN
        enterDown.KeyEvent.dwControlKeyState = 0
        inputRecords.Add(enterDown)

        Dim enterUp As New INPUT_RECORD()
        enterUp.EventType = INPUT_RECORD_TYPE.KEY_EVENT
        enterUp.KeyEvent.bKeyDown = False
        enterUp.KeyEvent.wVirtualKeyCode = &HD
        enterUp.KeyEvent.dwControlKeyState = 0
        inputRecords.Add(enterUp)

        ' 调用WriteConsoleInputW发送输入事件
        success = WriteConsoleInputW(hConsoleInput, inputRecords(0), inputRecords.Count, recordsWritten)
        If Not success Then
            MessageBox.Show($"WriteConsoleInput失败,错误码: {Marshal.GetLastWin32Error()}")
        Else
            MessageBox.Show($"成功写入{recordsWritten}条输入记录")
        End If

        ' 设置控制台标题
        SetConsoleTitleA(titleStr)
    Finally
        ' 一定要释放控制台,否则当前程序会和目标控制台绑定
        FreeConsole()
    End Try
End Sub

4. Wine下的额外注意事项

  • 确保你使用的Wine版本支持WriteConsoleInputW:大部分较新的Wine版本(比如6.0+)都支持这个API,但旧版本可能存在兼容性问题。
  • 推荐使用Unicode版本的API(WriteConsoleInputW),Windows和Wine对Unicode的支持比ANSI更稳定。
  • 附加控制台后必须调用FreeConsole,否则你的程序后续可能无法正常使用自身的控制台(如果有的话)。

内容的提问来源于stack exchange,提问作者MrHaribo

火山引擎 最新活动