使用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




