从 PySide GUI 应用程序 (py2exe) 调用子进程的应用程序停止工作

Application called with Subprocess from PySide GUI application (py2exe) stopped working

该程序的体系结构由使用 Py2exe 构建的 PySide GUI 可执行文件组成。此外,它还有第二个 python 使用 Py2exe 构建的控制台应用程序。

GUI-App 使用命名管道启动控制台应用程序,以便能够发送 CTRL_C 和捕获 stdout/stderr。这是使用 pywin32 模块完成的。

在控制台应用程序中,其他几个 Windows 应用程序使用 subprocess 模块以阻塞模式执行。现在,控制台应用程序通过一些代码进行了扩展,可以在非阻塞模式下执行另一个程序。

如果执行了 GUI 应用程序和控制台应用程序(在构建为 Py2exe 应用程序之前),一切都按预期和需要工作。 如果应用程序是使用 Py2exe 构建的,则外部 Windows 应用程序会冻结并弹出 windows 错误。 (应用程序停止工作)

如果隔离外部 Windows 应用程序的执行代码并构建 py2exe 可执行文件,则一切正常。

所以问题一定是 py2exe 和从 GUI 应用程序调用控制台应用程序。

我希望有人能指出我正确的方向。

我被要求提供一个最小的例子。问题是该体系结构实际上不允许最小的工作示例。我尽了最大努力,我希望给定的示例能够更清楚地说明问题。正如所解释的 通过测试问题可以归结为py2exe。一旦 GUI 应用程序 "compiled" 启动,控制台应用程序内的非阻塞应用程序就会运行,但新应用程序会导致 windows 错误。

最小示例:

控制台应用程序 console.py:

import logging
import subprocess
from time import sleep

def exec_program_nonblocking(args, cwd=None):
    if cwd:
        logging.debug("Change working directory to: %s", cwd)

    subprocess.Popen(args,
                 cwd=cwd,
                 universal_newlines=True)

def main():
    args = ["program.exe", "arg1", "arg2"]
    exec_program_nonblocking(args)
    while (True):
        print "do something \n"
        sleep(5)

if __name__ == '__main__':      
    main()

setup.py 控制台应用程序:

from distutils.core import setup
import py2exe

setup(console=['console.py'])

PySide GUI 应用程序gui_app.py:

from PySide.QtGui import QApplication

def start_console_app():
    pass
    # complex implementation of openening named pipe,
    # handling callbacks, allowing to send CTRL_C to new process etc.
    # Minimal example is not possible...

app = QtGui.QApplication([])

button = QtGui.QPushButton('Start Console App')
button.clicked.connect(start_console_app)
button.show()

app.exec_()

setup.py 对于 GUI 应用程序:

from distutils.core import setup
import py2exe

setup(
    options = {
        'py2exe' : {
            'packages' : 'encodings',
            'unbuffered' : True
            #dll_excludes....
        }
    },
    zipfile = None,

    windows = ['gui_app.py']
)

对我来说,here 发布的解决方案对我有用。 py2exe 以某种方式弄乱了从父进程(GUI 应用程序)传递到子进程的句柄。它甚至不会发生在所有作为控制台应用程序子进程的可执行文件上。

子进程模块提供了一个参数close_fds来防止子进程从父进程继承句柄。这不适用于我的应用程序。尽管如此,它必须与py2exe有关。

将 DETACHED_PROCESS 标志赋予参数 creationflags 打开一个新的控制台,进程成功启动!

唯一的缺点是,据我所知,stdout/stderr 无法捕获。

工作示例:

import subprocess

DETACHED_PROCESS = 0x00000008

subprocess.Popen(args,
                 cwd=cwd,
                 creationflags=DETACHED_PROCESS)