2

I'm able to interrupt my subprocesses in Windows with

import ctypes
ctypes.windll.kernel32.GenerateConsoleCtrlEvent(1, _proc.pid)

but only if I run it via normal Python process.

When I run the same code via a separate launcher program using Python C API (code is below), the code above doesn't have any effect.

Should I change my launcher somehow in order to be able to interrupt subprocesses?

#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;
}

EDIT: Turns out the same problems comes with pythonw.exe.

7
  • Unless the target pid is actually a process group ID, as is documented, then the behavior of GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid) is undefined. In practice what it does is act like pid is 0, i.e. it broadcasts the event to every process that's attached to a console. You don't want that. It could kill a parent process that's attached to the console. Commented Apr 17, 2017 at 20:16
  • As to your launcher, it isn't attached to a console at all, so I can't see why you would think GenerateConsoleCtrlEvent would work. Commented Apr 17, 2017 at 20:18
  • Your best bet is to use real IPC, for example a named event object. (Granted, I have no idea how to do that in Python.) Commented Apr 18, 2017 at 3:09
  • @eryksun , I'm creating the process with creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, so it works with normal Python. Any ideas, how to attach the launcher to a console? Commented Apr 18, 2017 at 3:17
  • @eryksun , The launcher is a GUI process, which uses python.exe to run background tasks. When I run my module via Eclipse Pydev, then no console window is seen but I can interrupt the backend. When I run it via my launcher, then console window pops up for backend, but I can't interrupt it. Commented Apr 18, 2017 at 3:38

2 Answers 2

2

That's how I finally got the console allocated without flashing console window (thanks to @eryksun for the pointers):

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

Basically I stole the console from a dummy subprocess.

Sign up to request clarification or add additional context in comments.

6 Comments

If you're going to use the cmd shell, you can do it more reliably than with timeouts. Have the shell echo to stdout and read from stdin. The parent reads from the child's stdout, attaches to the console, and closes stdin. For example:
child = subprocess.Popen('echo & 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()
In case anyone is wondering how the console is hidden in this case, the shell=True argument runs the command via %ComSpec% /c (typically cmd.exe) and also sets the STARTUPINFO to hide the window.
Thanks, @eryksun! I edited the answer according to your suggestion.
Oops, fixed now
|
0

I'll propose one possible solution according to @eryksun's comment.

Just do

import ctypes
ctypes.windll.kernel32.AllocConsole() 

in parent process.

Unfortunately (as eryksun also noted), this also creates unnecessary and confusing console window.

1 Comment

If you're doing it this way, you can hide the console using user32.ShowWindow(kernel32.GetConsoleWindow(), SW_HIDE). BTW, please define prototypes, starting with your own instances of the libraries, i.e. kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) and using ctypes.get_last_error() to check the captured error value.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.