【问题标题】:How to interrupt Python subprocesses on Windows when using Python C API?使用 Python C API 时如何在 Windows 上中断 Python 子进程?
【发布时间】:2017-04-17 11:46:15
【问题描述】:

我可以用

中断我在 Windows 中的子进程
import ctypes
ctypes.windll.kernel32.GenerateConsoleCtrlEvent(1, _proc.pid)

但前提是我通过正常的 Python 进程运行它。

当我使用 Python C API(代码如下)通过单独的启动程序运行相同的代码时,上面的代码没有任何效果。

我应该以某种方式更改我的启动器以便能够中断子进程吗?

#include <Python.h>
#include <windows.h>

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    LPWSTR *argv;
    int argc;

    argv = CommandLineToArgvW(GetCommandLine(), &argc);
    if (argv == NULL)
    {
        MessageBox(NULL, L"Unable to parse command line", L"Error", MB_OK);
        return 10;
    }

    Py_SetProgramName(argv[0]);
    Py_Initialize();
    PySys_SetArgvEx(argc, argv, 0);

    PyObject *py_main, *py_dict;
    py_main = PyImport_AddModule("__main__");
    py_dict = PyModule_GetDict(py_main);

    PyObject* result = PyRun_String(
        "from runpy import run_module\n"
        "run_module('thonny')\n",
        Py_file_input,
        py_dict,
        py_dict
        );

    int code;
    if (!result) {
        PyObject *ptype, *pvalue, *ptraceback;
        PyErr_Fetch(&ptype, &pvalue, &ptraceback);

        PyObject* valueAsString = PyObject_Str(pvalue);

        wchar_t* error_msg = PyUnicode_AsWideCharString(valueAsString, NULL);
        MessageBox(0, error_msg, L"Thonny startup error", MB_OK | MB_ICONERROR);
        code = -1;
    }
    else {
        code = 1;
    }

    Py_Finalize();

    return code;
}

编辑:原来pythonw.exe 也有同样的问题。

【问题讨论】:

  • 除非目标pid 实际上是一个进程 ID,如文档所述,否则GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid) 的行为是未定义的。实际上,它的作用就像pid 为 0,即它将事件广播到连接到控制台的每个进程。你不想要那个。它可能会杀死附加到控制台的父进程。
  • 至于您的启动器,它根本没有连接到控制台,所以我不明白您为什么认为GenerateConsoleCtrlEvent 会起作用。
  • 最好的办法是使用真正的 IPC,例如命名事件对象。 (当然,我不知道如何在 Python 中做到这一点。)
  • @eryksun ,我正在使用creationflags=subprocess.CREATE_NEW_PROCESS_GROUP 创建进程,因此它适用于普通 Python。任何想法,如何将启动器附加到控制台?
  • @eryksun , 启动器是一个 GUI 进程,它使用 python.exe 来运行后台任务。当我通过 Eclipse Pydev 运行我的模块时,没有看到控制台窗口,但我可以中断后端。当我通过启动器运行它时,后端会弹出控制台窗口,但我无法中断它。

标签: python windows subprocess interrupt


【解决方案1】:

这就是我最终在不闪烁控制台窗口的情况下分配控制台的方式(感谢@eryksun 的指点):

import sys
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

cmd = [sys.executable, "-c", "print('Hi!'); input()"]
child = subprocess.Popen(cmd,
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         shell=True)

child.stdout.readline() # now I know subprocess is ready
result = kernel32.AttachConsole(child.pid)
if not result:
    err = ctypes.get_last_error()
    print("Could not allocate console. Error code:", err, file=sys.stderr)
child.stdin.write(b"\n") # allow subprocess to complete
child.stdin.flush()

基本上我从一个虚拟子进程中窃取了控制台。

【讨论】:

  • 如果你打算使用 cmd shell,你可以比使用超时更可靠。让 shell 回显到标准输出并从标准输入读取。父母从孩子的标准输出中读取,附加到控制台,然后关闭标准输入。例如:
  • child = subprocess.Popen('echo &amp; set /p "x="', stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True);child.stdout.read(1);result = kernel32.AttachConsole(child.pid);child.stdin.close();if child.poll() is None: child.kill()
  • 如果有人想知道在这种情况下控制台是如何隐藏的,shell=True 参数通过%ComSpec% /c(通常是 cmd.exe)运行命令,并且 alsoSTARTUPINFO 隐藏窗口。
  • 谢谢@eryksun!我根据您的建议编辑了答案。
  • 糟糕,现已修复
【解决方案2】:

我会根据@eryksun 的评论提出一种可能的解决方案。

做吧

import ctypes
ctypes.windll.kernel32.AllocConsole() 

在父进程中。

不幸的是(正如 eryksun 也指出的那样),这也会创建不必要且令人困惑的控制台窗口。

【讨论】:

  • 如果你这样做,你可以使用user32.ShowWindow(kernel32.GetConsoleWindow(), SW_HIDE)隐藏控制台。顺便说一句,请定义原型,从您自己的库实例开始,即 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) 并使用 ctypes.get_last_error() 检查捕获的错误值。
猜你喜欢
  • 2016-06-23
  • 1970-01-01
  • 2013-05-10
  • 2015-04-17
  • 2010-10-29
  • 2011-10-28
  • 1970-01-01
  • 2019-02-26
相关资源
最近更新 更多