4

我正在努力让它发挥作用,希望你们中的一个人以前做过。

我有一个 C# 控制台应用程序,它正在运行一个继承其控制台的子进程。我希望将外部应用程序捕获的 ctrl-c 传递给内部应用程序,以便它有机会很好地关闭。

我有一些非常简单的代码。我启动一个进程,然后使用 WaitForExit(10) 轮询它。我还注册了一个 CancelKeyPress 处理程序,它在触发时将 bool 设置为 true。轮询循环也会检查这一点,当它为真时,它会调用 GenerateConsoleCtrlEvent()(我已通过 pinvoke 映射)。

我已经尝试了很多参数组合到 GenerateConsoleCtrlEvent()。第一个参数为 0 或 1,第二个参数为 0 或子进程的 ID。似乎没有任何效果。有时我得到一个错误的回复并且 Marshal.GetLastWin32Error() 返回 0,有时我得到一个真实的回复。但没有一个会导致子应用程序收到 ctrl-c。

可以肯定的是,我编写了一个测试 C# 应用程序作为子应用程序,它打印出它发生了什么,并验证了在它运行时手动键入 ctrl-c 确实会导致它退出。

几个小时以来,我一直在努力解决这个问题。谁能给我一些关于该去哪里的指示?

4

3 回答 3

3

不太确定这是一个好方法。这仅适用于使用 CREATE_NEW_PROCESS_GROUP 标志创建的子进程CreateProcess()。但是 System.Diagnostics.Process 类不支持这一点。

考虑使用 Main() 方法的返回值。Windows SDK 中已经为 Ctrl+C 中止定义了一个唯一值,即 STATUS_CONTROL_C_EXIT 或 0xC000013A。父进程可以从Process.ExitCode属性中获取该返回码。

于 2008-11-18T02:04:04.283 回答
2

你有这个运气吗?我的理解是,当您在控制台中按 CTRL+C 时,默认情况下,连接到控制台的所有进程都会收到它,而不仅仅是父进程。这是一个例子:

儿童.cs:

using System;

public class MyClass
{
    public static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
    {
        Console.WriteLine("Child killed by CTRL+C.");
    }
    public static void Main()
    {
        Console.WriteLine("Child start.");
        Console.CancelKeyPress += CtrlCHandler;
        System.Threading.Thread.Sleep(4000);
        Console.WriteLine("Child finish.");
    }
}

父.cs:

using System;

public class MyClass
{
    public static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
    {
        Console.WriteLine("Parent killed by CTRL+C.");
    }
    public static void Main()
    {
        Console.CancelKeyPress += CtrlCHandler;
        Console.WriteLine("Parent start.");
        System.Diagnostics.Process child = new System.Diagnostics.Process();
        child.StartInfo.UseShellExecute = false;
        child.StartInfo.FileName = "child.exe";
        child.Start();
        child.WaitForExit();
        Console.WriteLine("Parent finish.");
    }
}

输出:

Y:\>parent
Parent start.
Child start.
Parent killed by CTRL+C.
Child killed by CTRL+C.
^C
Y:\>parent
Parent start.
Child start.
Child finish.
Parent finish.

所以我不会认为你需要做任何特别的事情。但是,如果您真的需要自己生成 CTRL+C 事件,事情可能就没有那么容易了。我不确定您描述的问题,但据我所知,您只能将 CTRL+C 事件发送到附加到控制台窗口的所有进程。如果分离进程,则无法向其发送 CTRL+C 事件。如果您想选择在哪些进程中发送 CTRL+C 事件,您似乎需要为每个进程创建新的控制台窗口。我不知道是否有某种方法可以在没有可见窗口的情况下执行此操作,或者当您想使用管道重定向 I/O 时。

于 2009-11-26T10:37:20.833 回答
0

这是我将 ctrl-c 发送到进程的解决方案。仅供参考,我从来没有GenerateConsoleCtrlEvent工作过。

不是使用 GenerateConsoleCtrlEvent,而是我发现将 CTRL-C 发送到进程的方法。仅供参考,在这种情况下,我不需要找到组进程 ID。

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public class ConsoleAppManager
{
    private readonly string appName;
    private readonly Process process = new Process();
    private readonly object theLock = new object();
    private SynchronizationContext context;
    private string pendingWriteData;

    public ConsoleAppManager(string appName)
    {
        this.appName = appName;

        this.process.StartInfo.FileName = this.appName;
        this.process.StartInfo.RedirectStandardError = true;
        this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;

        this.process.StartInfo.RedirectStandardInput = true;
        this.process.StartInfo.RedirectStandardOutput = true;
        this.process.EnableRaisingEvents = true;
        this.process.StartInfo.CreateNoWindow = true;

        this.process.StartInfo.UseShellExecute = false;

        this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;

        this.process.Exited += this.ProcessOnExited;
    }

    public event EventHandler<string> ErrorTextReceived;
    public event EventHandler ProcessExited;
    public event EventHandler<string> StandartTextReceived;

    public int ExitCode
    {
        get { return this.process.ExitCode; }
    }

    public bool Running
    {
        get; private set;
    }

    public void ExecuteAsync(params string[] args)
    {
        if (this.Running)
        {
            throw new InvalidOperationException(
                "Process is still Running. Please wait for the process to complete.");
        }

        string arguments = string.Join(" ", args);

        this.process.StartInfo.Arguments = arguments;

        this.context = SynchronizationContext.Current;

        this.process.Start();
        this.Running = true;

        new Task(this.ReadOutputAsync).Start();
        new Task(this.WriteInputTask).Start();
        new Task(this.ReadOutputErrorAsync).Start();
    }

    public void Write(string data)
    {
        if (data == null)
        {
            return;
        }

        lock (this.theLock)
        {
            this.pendingWriteData = data;
        }
    }

    public void WriteLine(string data)
    {
        this.Write(data + Environment.NewLine);
    }

    protected virtual void OnErrorTextReceived(string e)
    {
        EventHandler<string> handler = this.ErrorTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    protected virtual void OnProcessExited()
    {
        EventHandler handler = this.ProcessExited;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    protected virtual void OnStandartTextReceived(string e)
    {
        EventHandler<string> handler = this.StandartTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    private void ProcessOnExited(object sender, EventArgs eventArgs)
    {
        this.OnProcessExited();
    }

    private async void ReadOutputAsync()
    {
        var standart = new StringBuilder();
        var buff = new char[1024];
        int length;

        while (this.process.HasExited == false)
        {
            standart.Clear();

            length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
            standart.Append(buff.SubArray(0, length));
            this.OnStandartTextReceived(standart.ToString());
            Thread.Sleep(1);
        }

        this.Running = false;
    }

    private async void ReadOutputErrorAsync()
    {
        var sb = new StringBuilder();

        do
        {
            sb.Clear();
            var buff = new char[1024];
            int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
            sb.Append(buff.SubArray(0, length));
            this.OnErrorTextReceived(sb.ToString());
            Thread.Sleep(1);
        }
        while (this.process.HasExited == false);
    }

    private async void WriteInputTask()
    {
        while (this.process.HasExited == false)
        {
            Thread.Sleep(1);

            if (this.pendingWriteData != null)
            {
                await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
                await this.process.StandardInput.FlushAsync();

                lock (this.theLock)
                {
                    this.pendingWriteData = null;
                }
            }
        }
    }
}

然后,在我的主应用程序中实际运行进程并发送 CTRL-C:

            DateTime maxStartDateTime = //... some date time;
            DateTime maxEndDateTime = //... some later date time
            var duration = maxEndDateTime.Subtract(maxStartDateTime);
            ConsoleAppManager appManager = new ConsoleAppManager("myapp.exe");
            string[] args = new string[] { "args here" };
            appManager.ExecuteAsync(args);
            await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000);

            if (appManager.Running)
            {
                // If stilll running, send CTRL-C
                appManager.Write("\x3");
            }

详情请参见重定向控制台应用程序的标准输入Windows如何获取已经在运行的进程的进程组?

于 2018-05-12T22:55:25.773 回答