如何干净地关闭控制台应用程序开始的Process.Start?(How to cleanly shu

2019-08-31 17:40发布

这看起来像一个不可能完成的任务。 绝对没有,我发现作品。 现在的问题是如何干净地关闭控制台应用程序启动时已启动,没有控制台窗口的Process.Start,并且不使用命令行下执行:( ProcessStartInfo.CreateNoWindow = true; ProcessStartInfo.UseShellExecute = false;

这是因为该应用程序正在启动将关闭“干净”,如果它收到一个CTRL-C或Ctrl-中断信号,但似乎没有办法送它一个工程(特别是GenerateConsoleCtrlEvent)。

  • Process.Kill不起作用。 它留下由于过程突然杀损坏的文件。
  • Process.CloseMainWindow不起作用。 有在这种情况下,没有主窗口,因此函数返回false,什么都不做。
  • 呼吁所有线程EnumThreadWindows的进程和发送WM_CLOSE到每一个窗口不执行且没有任何线程窗口反正。
  • GenerateConsoleCtrlEvent不起作用。 这仅适用于在同一组(在.NET让你无法控制的)过程中有用,反正与关闭调用进程的不良副作用。 此功能不会允许你指定一个进程ID。

凡能提供一个接受“过程”对象开始与上述这导致启动的过程的干净关机,而不影响调用进程将被标记为答案的参数的代码。 使用7z.exe(7-ZIP归档)为例控制台应用程序,它开始压缩大文件,并会留下如果不干净终止的损坏,未完成的文件。

直到有人提供了通向功能示例的功能例子或代码,这个问题没有答案。 我看到数十人在问这个问题,几十答案在线,没有他们的工作。 .NET似乎提供了合理的关闭控制台应用程序给它的进程ID,这是奇怪考虑它开始用.NET Process对象不支持。 问题的部分是不能在一个新的进程组,这使得使用GenerateConsoleCtrlEvent没用的创建进程。 必须有这个解决方案。

Answer 1:

这是一个有点晚,所以你可能不会再使用它,但也许这将帮助别人...

你是这个得太多。 问题是,你只能从信号共用同一个控制台的过程中断 - 的解决方案应该是相当明显的。

创建一个控制台项目。 该项目将启动目标应用程序的常用方法:

var psi = new ProcessStartInfo();
psi.FileName = @"D:\Test\7z\7z.exe";
psi.WorkingDirectory = @"D:\Test\7z\";
psi.Arguments = "a output.7z input.bin";
psi.UseShellExecute = false;

var process = Process.Start(psi);

UseShellExecute是重要的组成部分-这确保了两个应用程序都将共享相同的控制台。

这可以让你休息发送到您的辅助应用程序,这将被传递给托管的应用程序,以及:

Console.CancelKeyPress += (s, e) => e.Cancel = true;
Thread.Sleep(1000);
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);

这将打破它开始了第二次后,托管的应用程序。 方便,安全。 该CancelKeyPress不是必需的-我只是把它放在那里,使之明显,你可以打破托管过程中,仍然保持运行。 在真正的帮助应用程序,这可以用于一些通知或类似的东西,但它不是真正需要的。

现在你只需要一种方法来信号的辅助应用程序发出中断命令 - 最简单的方法是只用一个简单的控制台输入,但可能被托管的应用程序造成干扰。 如果这不是一个选择,一个简单的互斥体将正常工作:

using (var mutex = Mutex.OpenExisting(args[0]))
using (var processWaitHandle = new SafeWaitHandle(process.Handle, false))
using (var processMre = new ManualResetEvent(false) { SafeWaitHandle = processWaitHandle })
{
    var which = WaitHandle.WaitAny(new WaitHandle[] { mutex, processMre });

    if (which == 0)
    {
        Console.WriteLine("Got signalled.");
        GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);
    }
    else if (which == 1)
    {
        Console.WriteLine("Exitted normally.");
    }
}

这将等待无论是互斥的信号,或者被托管应用程序退出。 要启动辅助应用程序,所有你需要做的是这样的:

var mutexName = Guid.NewGuid().ToString();
mutex = new Mutex(true, mutexName);

var process = Process.Start(@"TestBreak.exe", mutexName);

并发出破,只是释放互斥锁:

mutex.ReleaseMutex();

而已。 如果您需要更严格的控制,使用类似命名的管道可能是一个更好的选择,但如果你只需要休息的信号,互斥会做得很好。 你需要的任何参数传递可以作为参数来帮助应用程序传递,你甚至可以使shell脚本这项工作(只是使用助手应用程序来运行任何你需要运行,打破)。



Answer 2:

我花了几个小时试图找出这一个自己。 至于你提到的网站是充斥着根本不起作用答案。 很多人建议使用GenerateConsoleCtrlEvent,但他们没有提供任何情况下,他们只是提供无用的代码片段。 下面的解决方案使用GenerateConsoleCtrlEvent,但它的作品。 我测试过它。

请注意,这是一个WinForms应用程序,我开始和停止的过程FFmpeg的 。 我没有测试与任何其他的解决方案。 我使用的FFmpeg这里录制视频并输出保存到一个名为“video.mp4”文件。

下面的代码是我Form1.cs的文件的内容。 这是当你创建一个WinForms的解决方案,Visual Studio中创建您的文件。

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleProcessShutdownDemo {
    public partial class Form1 : Form {

    BackgroundWorker worker;
    Process currentProcess;

    public Form1() {
        InitializeComponent();
    }

    private void Worker_DoWork(object sender, DoWorkEventArgs e) {
        const string outFile = "video.mp4";

        var info = new ProcessStartInfo();
        info.UseShellExecute = false;
        info.CreateNoWindow = true;
        info.FileName = "ffmpeg.exe";
        info.Arguments = string.Format("-f gdigrab -framerate 60 -i desktop -crf 0 -pix_fmt yuv444p -preset ultrafast {0}", outFile);
        info.RedirectStandardInput = true;

        Process p = Process.Start(info);

        worker.ReportProgress(-1, p);
    }

    private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
        currentProcess = (Process)e.UserState;
    }

    private void btnStart_Click(object sender, EventArgs e) {
        btnStart.Enabled = false;
        btnStop.Enabled = true;

        worker = new BackgroundWorker();

        worker.WorkerSupportsCancellation = true;
        worker.WorkerReportsProgress = true;
        worker.DoWork += Worker_DoWork;
        worker.ProgressChanged += Worker_ProgressChanged;

        worker.RunWorkerAsync();

    }

    private void btnStop_Click(object sender, EventArgs e) {
        btnStop.Enabled = false;
        btnStart.Enabled = true;

        if (currentProcess != null)
            StopProgram(currentProcess);
    }





    //MAGIC BEGINS


    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AttachConsole(uint dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern bool FreeConsole();

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

    [DllImport("Kernel32", SetLastError = true)]
    private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);

    enum CtrlTypes {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    private delegate bool HandlerRoutine(CtrlTypes CtrlType);

    public void StopProgram(Process proc) {

        int pid = proc.Id;

        FreeConsole();

        if (AttachConsole((uint)pid)) {

            SetConsoleCtrlHandler(null, true);
            GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

            Thread.Sleep(2000);

            FreeConsole();

            SetConsoleCtrlHandler(null, false);
        }

        proc.WaitForExit();

        proc.Close();
    }


    //MAGIC ENDS
}

}



文章来源: How to cleanly shut down a console app started with Process.Start?