如何在Windows上使用Python控制交互式控制台的输入/输出?

4
我需要控制一个Windows程序,该程序通过调用<conio.h>中的_kbhit_getch从控制台直接读取输入。这里可以找到这样一个程序的示例:https://dev59.com/uHDXa4cB1Zd3GeqP8BK_#15603102 在Linux上,我可以使用pty.openpty()创建新的伪终端并模拟按键。请参见此示例:https://code.google.com/p/lilykde/source/browse/trunk/lilykde/py/runpty.py 在Windows上,我尝试写入CONIN$/CONOUT$,但我所看到的只是我的数据出现在控制台上,而子进程忽略它。
以下是代码:
#!/usr/bin/env python

import subprocess
import time

TEST_EXECUTABLE = 'C:\\dev\\test.exe'
TEST_INPUT = 'C:\\dev\\input.txt'


def main():
    with open(TEST_INPUT, mode='r') as test_input, open('CONOUT$', mode='wb') as conout:
        test_exec = subprocess.Popen([TEST_EXECUTABLE],
                                     bufsize=0,
                                     stdin=None,
                                     stdout=None,
                                     stderr=None)

        for cmd in test_input:
            cmd = cmd.strip('\r\n')
            conout.write(cmd)
            conout.flush()
            time.sleep(1)

        ret = test_exec.wait()
        print '%s (%d): %d' % (TEST_EXECUTABLE, test_exec.pid, ret)

    pass

if __name__ == "__main__":
    main()

这可能吗?我如何模拟与子进程的用户交互?

谢谢。 Alex

1个回答

3

我找到了答案。不幸的是,没有内置模块可以做到这一点,所以我必须使用 ctypes 和一些 Win32 API 来完成。以下是代码:

#!/usr/bin/env python

from ctypes import *
import msvcrt
import os
import subprocess
import time

TEST_EXECUTABLE = 'C:\\dev\\test.exe'
TEST_INPUT = 'C:\\dev\\input.txt'

# input event types
KEY_EVENT = 0x0001

# constants, flags
MAPVK_VK_TO_VSC = 0

# structures
class CHAR_UNION(Union):
    _fields_ = [("UnicodeChar", c_wchar),
                ("AsciiChar", c_char)]

    def to_str(self):
        return ''


class KEY_EVENT_RECORD(Structure):
    _fields_ = [("bKeyDown", c_byte),
                ("pad2", c_byte),
                ("pad1", c_short),
                ("wRepeatCount", c_short),
                ("wVirtualKeyCode", c_short),
                ("wVirtualScanCode", c_short),
                ("uChar", CHAR_UNION),
                ("dwControlKeyState", c_int)]

    def to_str(self):
        return ''


class INPUT_UNION(Union):
    _fields_ = [("KeyEvent", KEY_EVENT_RECORD)]

    def to_str(self):
        return ''


class INPUT_RECORD(Structure):
    _fields_ = [("EventType", c_short),
                ("Event", INPUT_UNION)]

    def to_str(self):
        return ''


def write_key_to_console(hcon, key):
    li = INPUT_RECORD * 2
    list_input = li()

    ke = KEY_EVENT_RECORD()
    ke.bKeyDown = c_byte(1)
    ke.wRepeatCount = c_short(1)

    cnum = ord(key)
    ke.wVirtualKeyCode = windll.user32.VkKeyScanW(cnum)
    ke.wVirtualScanCode = c_short(windll.user32.MapVirtualKeyW(int(cnum),
                                                               MAPVK_VK_TO_VSC))
    ke.uChar.UnicodeChar = unichr(cnum)
    kc = INPUT_RECORD(KEY_EVENT)
    kc.Event.KeyEvent = ke
    list_input[0] = kc

    list_input[1] = list_input[0]
    list_input[1].Event.KeyEvent.bKeyDown = c_byte(0)

    events_written = c_int()
    ret = windll.kernel32.WriteConsoleInputW(hcon,
                                             list_input,
                                             2,
                                             byref(events_written))

    return ret


def main():
    with open(TEST_INPUT, mode='r') as test_input:
        fdcon = os.open('CONIN$', os.O_RDWR | os.O_BINARY)
        hconin = msvcrt.get_osfhandle(fdcon)

        test_exec = subprocess.Popen([TEST_EXECUTABLE])

        for cmd in test_input:
            cmd = cmd.strip('\r\n')
            write_key_to_console(hconin, cmd)
            time.sleep(1)

        os.close(fdcon)
        ret = test_exec.wait()

        print '%s (%d): %d' % (TEST_EXECUTABLE, test_exec.pid, ret)

    pass


if __name__ == "__main__":
    main()

input.txt 文件每行包含一个字符。可以轻松地扩展 write_key_to_console 函数以一次写入多个字符。

如果调用进程没有控制台或其控制台与子进程不同,则需要使用子进程 ID 作为参数调用 AttachConsole 函数,然后再打开 CONIN$ 文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接