【问题标题】:How to control interactive console input/output from Python on Windows?如何在 Windows 上控制 Python 的交互式控制台输入/输出?
【发布时间】:2014-02-28 00:48:03
【问题描述】:

我需要控制一个 Windows 程序,该程序通过从 <conio.h> 调用 _kbhit_getch 直接从控制台读取输入。可以在此处找到此类程序的示例:https://stackoverflow.com/a/15603102/365492

在 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()

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

谢谢。 亚历克斯

【问题讨论】:

    标签: python windows terminal console pty


    【解决方案1】:

    我找到了答案。不幸的是,没有内置模块可以做到这一点,所以我不得不使用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 函数可以很容易地扩展为一次写入多个字符。

    如果调用进程没有控制台或者它的控制台与子进程不同,那么我们需要在打开CONIN$文件之前以子进程ID为参数调用AttachConsole函数。

    【讨论】:

      猜你喜欢
      • 2011-09-13
      • 1970-01-01
      • 1970-01-01
      • 2012-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-12
      相关资源
      最近更新 更多