C#传递Ctrl-C信号给Win32子程序(Console App)

应用场景:

C# WPF程序通过System.Diagnostics.Process启动一个控制台程序,该子进程需要接收键盘输入Ctrl-C退出。需要在C#程序中模拟键盘输入Ctrl-C传递给子程序,然后通过Process.StandardOutput获取子程序输出。

在微软文档中提供了以下函数操作Console: 

以下是具体代码

// 导入Win32 Console函数
[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")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);

[DllImport("kernel32.dll")]
static extern bool AllocConsole();

delegate Boolean ConsoleCtrlDelegate(CtrlTypes type);

// 控制消息
enum CtrlTypes : uint
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public static void StopProcess(Process proc)
{
    // 以防父进程已经attach到另一个Console,先调一次FreeConsole
    FreeConsole();

    // 一个进程最多只能attach到一个Console,否则失败,返回0
    if (AttachConsole((uint)proc.Id))
    {
        // 设置父进程属性,忽略Ctrl-C信号
        SetConsoleCtrlHandler(null, true);

        // 发出一个Ctrl-C到共享该控制台的所有进程中
        GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

        // 父进程与控制台分离,此时子进程控制台收到Ctrl-C关闭
        FreeConsole();

        // 现在父进程没有Console,为它新建一个
        AllocConsole();

        // 等待子进程退出
        proc.WaitForExit(2000);

        // 恢复父进程处理Ctrl-C信号
        SetConsoleCtrlHandler(null, false);

        // C#版的GetLastError()
        var lastError = Marshal.GetLastWin32Error();
    }
}

原理:

  1. 控制台应用程序可以看成由两部分组成:主程序+控制台窗口,主程序可以调用函数对控制台进行各种操作,上面列出的4个函数也包含在其中Console Functions
  2. 由于主程序只能attach到最多一个Console,这里先调用一次FreeConsole,然后将父进程attach到子进程控制台。如果父进程也是一个控制台程序,当启动子控制台程序时,子进程会attach到父进程的控制台,这里就不需要FreeConsole,下面的判断语句也需要修改。
  3. 接下来调用SetConsoleCtrlHandler(null, true)设置父进程的属性忽略Ctrl-C信号,注意这个属性是可以继承的,所以子进程控制台也会忽略Ctrl-C信号。
  4. GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0),“0”表示在共享主进程控制台的所有进程中生成Ctrl-C信号。由于上一步设置了属性,所以到这里子进程还不会响应Ctrl-C信号。
  5. 第二个FreeConsole让父进程脱离子进程的控制台,子程序不再继承父进程设置的属性,子程序响应Ctrl-C信号退出。(猜测:这里控制台接收到了Ctrl-C信号,保存起来没有处理,当父进程设置的属性消失后,控制台才响应Ctrl-C信号)
  6. 到此,父进程没有控制台窗口,但是可以继续运行。如果父进程需要,我们可以给他新建一个控制台,这个新建的控制台没有Ctrl-C信号,所以当执行SetConsoleCtrlHandler(null, false)删除属性恢复处理Ctrl-C信号时,父进程不会退出。

仅供参考,若有错误,请不吝赐教!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值