2011-08-16 58 views
21

我有一個測試工具(用Python編寫),需要關閉被測試程序(用C語言編寫),發送它^C。在Unix上,在Windows上發送^ C到Python子進程對象

proc.send_signal(signal.SIGINT) 

完美地工作。在Windows上,會引發錯誤(「不支持信號2」或類似的東西)。我使用Python 2.7的Windows,所以我的印象中,我應該能夠做到,而不是

proc.send_signal(signal.CTRL_C_EVENT) 

但這並不做任何事情。我需要做什麼?這是創建子進程的代碼:

# Windows needs an extra argument passed to subprocess.Popen, 
# but the constant isn't defined on Unix. 
try: kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP 
except AttributeError: pass 
proc = subprocess.Popen(argv, 
         stdin=open(os.path.devnull, "r"), 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE, 
         **kwargs) 
+0

這可能是唯一的出路 - http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/用WIN32API OH或ctypes的。 – arunkumar

+0

'subprocess.kill'將會爲我調用'TerminateProcess'就好,但不會生成^ C。我特別需要僞裝在控制檯輸入^ C的行爲。 – zwol

+0

試試這個 - http://www.rutherfurd.net/python/sendkeys/。顯然SendKeys.SendKeys(「^ c」)應該這樣做。 – arunkumar

回答

7

嘗試調用使用​​的GenerateConsoleCtrlEvent功能。在創建新進程組時,進程組ID應與pid相同。所以,像

import ctypes 

ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, proc.pid) # 0 => Ctrl-C 

應該工作。

更新:你說得對,我錯過了那部分細節。這是a post這表明可能的解決方案,雖然它有點kludgy。更多詳情請見this answer

+0

這也沒有用,所以我重讀了MSDN頁面,並且意識到它特別說「你不能發送CTRL_C_EVENT到一個進程組,它沒有任何作用」 。發送CTRL_BREAK_EVENT可以工作(甚至不用ctypes),並且在玩具測試程序中正是我想要的,但是當我在真正的測試程序中使用它時,我會遇到「遇到問題並需要關閉」對話框一遍又一遍的重複。有任何想法嗎? – zwol

+0

@zwol:docs say *「此信號無法爲進程組生成。」*但是'os.kill(0,signal.CTRL_C_EVENT)'在Windows控制檯的'ipython'中爲我生成'KeyboardInterrupt',就像我'手動按下了Ctrl + C,即可以使用'CTRL_C_EVENT'和'0'(*「該信號在共享調用進程控制檯的所有進程中生成。」*)。 – jfs

10

有一個使用包裝的解決方案(如鏈接Vinay中所述),該解決方案通過Windows 開始命令在新的控制檯窗口中啓動。

碼包裝的:

#demo.py 

import signal, sys, time 

def signal_handler(signal, frame): 
    print 'Ctrl+C received in demo.py' 
    time.sleep(1) 
    sys.exit(0) 

signal.signal(signal.SIGINT, signal_handler) 
print 'demo.py started' 
#signal.pause() # does not work under Windows 
while(True): 
    time.sleep(1) 

啓動包裝例如像:

PythonPrompt> import subprocess 
PythonPrompt> subprocess.Popen("start python wrapper.py", shell=True) 

您需要添加一些

#wrapper.py 
import subprocess, time, signal, sys, os 

def signal_handler(signal, frame): 
    time.sleep(1) 
    print 'Ctrl+C received in wrapper.py' 

signal.signal(signal.SIGINT, signal_handler) 
print "wrapper.py started" 
subprocess.Popen("python demo.py") 
time.sleep(3) #Replace with your IPC code here, which waits on a fire CTRL-C request 
os.kill(signal.CTRL_C_EVENT, 0) 

程序捕獲CTRL-C的代碼IPC代碼,它允許您控制封裝器觸發os.kill(signal.CTRL_C_EVENT,0)命令。我在我的應用程序中爲此使用了套接字。

說明:

Preinformation

  • send_signal(CTRL_C_EVENT)不起作用,因爲CTRL_C_EVENT僅供os.kill[REF1]
  • os.kill(CTRL_C_EVENT)將信號發送到在當前cmd窗口[REF2]
  • Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP)運行,因爲CTRL_C_EVENT爲進程組忽略不工作的所有進程。[REF2] 這是Python文檔[REF3]

實施的解決方案中的錯誤

  1. 讓與Windows外殼程序指令以不同的cmd窗口程序運行開始
  2. 在控件應用程序和應該獲得CTRL-C信號的應用程序之間添加一個CTRL-C請求包裝器。包裝器將運行在與獲得CTRL-C信號的應用程序相同的cmd窗口中。
  3. 包裝程序將自行關閉程序,該程序應該通過在cmd窗口中發送CTRL_C_EVENT的所有進程來獲得CTRL-C信號。
  4. 控制程序應該能夠請求包裝發送CTRL-C信號。這可以通過IPC手段來實現,例如,插座。

有用的帖子是:

我不得不刪除HTTP中的鏈接的前面,因爲我是一個新用戶,並不得張貼兩個以上的鏈路。

更新:基於IPC的CTRL-C包裝

在這裏你可以找到一個自寫的python模塊,提供一個CTRL-C環繞包括一個基於套接字的IPC。 語法與子流程模塊非常相似。

用法:

>>> import winctrlc 
>>> p1 = winctrlc.Popen("python demo.py") 
>>> p2 = winctrlc.Popen("python demo.py") 
>>> p3 = winctrlc.Popen("python demo.py") 
>>> p2.send_ctrl_c() 
>>> p1.send_ctrl_c() 
>>> p3.send_ctrl_c() 

代碼

import socket 
import subprocess 
import time 
import random 
import signal, os, sys 


class Popen: 
    _port = random.randint(10000, 50000) 
    _connection = '' 

    def _start_ctrl_c_wrapper(self, cmd): 
    cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port) 
    subprocess.Popen(cmd_str, shell=True) 

    def _create_connection(self): 
    self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    self._connection.connect(('localhost', self._port)) 

    def send_ctrl_c(self): 
    self._connection.send(Wrapper.TERMINATION_REQ) 
    self._connection.close() 

    def __init__(self, cmd): 
    self._start_ctrl_c_wrapper(cmd) 
    self._create_connection() 


class Wrapper: 
    TERMINATION_REQ = "Terminate with CTRL-C" 

    def _create_connection(self, port): 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    s.bind(('localhost', port)) 
    s.listen(1) 
    conn, addr = s.accept() 
    return conn 

    def _wait_on_ctrl_c_request(self, conn): 
    while True: 
     data = conn.recv(1024) 
     if data == self.TERMINATION_REQ: 
     ctrl_c_received = True 
     break 
     else: 
     ctrl_c_received = False 
    return ctrl_c_received 

    def _cleanup_and_fire_ctrl_c(self, conn): 
    conn.close() 
    os.kill(signal.CTRL_C_EVENT, 0) 

    def _signal_handler(self, signal, frame): 
    time.sleep(1) 
    sys.exit(0) 

    def __init__(self, cmd, port): 
    signal.signal(signal.SIGINT, self._signal_handler) 
    subprocess.Popen(cmd) 
    conn = self._create_connection(port) 
    ctrl_c_req_received = self._wait_on_ctrl_c_request(conn) 
    if ctrl_c_req_received: 
     self._cleanup_and_fire_ctrl_c(conn) 
    else: 
     sys.exit(0) 


if __name__ == "__main__": 
    command_string = sys.argv[1] 
    port_no = int(sys.argv[2]) 
    Wrapper(command_string, port_no) 
+0

這種技術(父母向自己及其相關流程發送Ctrl + C)真的有用!我獨立地到達它,但是我發現一個人應該等待父代,直到SIGINT被處理,以避免信號中斷,例如,系統調用一旦到達。 – aknuds1

+0

參數的順序是錯誤的。它應該是'os.kill(pid,sig)'而不是'os.kill(sig,pid)'。雖然'os.kill(0,signal.CTRL_C_EVENT)'不會在vm中用Python 7中的Python 3.5中斷'input()'調用(意圖是將Ctrl + C發送到共享控制檯的所有進程) – jfs

0

我一直在嘗試這一點,但由於某種原因CTRL + BREAK作品,CTRL + C不會。所以使用os.kill(signal.CTRL_C_EVENT, 0)失敗,但做os.kill(signal.CTRL_C_EVENT, 1)的作品。我被告知這與創建進程所有者是唯一一個可以傳遞ctrl c有關的東西?那有意義嗎?

爲了澄清,在命令窗口中手動運行fio時,它似乎按預期運行。按預期方式使用CTRL + BREAK中斷而不存儲日誌,而CTRL + C也按預期寫入文件。問題出現在CTRL_C_EVENT的信號中。

它幾乎看起來像是Python中的一個bug,但可能是Windows中的一個bug。還有一件事,我有一個cygwin的版本運行,並在python中發送ctrl + c那裏工作,但是,然後再次我們不真的在那裏運行本地窗口。

例如:

import subprocess, time, signal, sys, os 
command = '"C:\\Program Files\\fio\\fio.exe" --rw=randrw --bs=1M --numjobs=8 --iodepth=64 --direct=1 ' \ 
    '--sync=0 --ioengine=windowsaio --name=test --loops=10000 ' \ 
    '--size=99901800 --rwmixwrite=100 --do_verify=0 --filename=I\\:\\test ' \ 
    '--thread --output=C:\\output.txt' 
def signal_handler(signal, frame): 
    time.sleep(1) 
    print 'Ctrl+C received in wrapper.py' 

signal.signal(signal.SIGINT, signal_handler) 
print 'command Starting' 
subprocess.Popen(command) 
print 'command started' 
time.sleep(15) 
print 'Timeout Completed' 
os.kill(signal.CTRL_C_EVENT, 0)