如何CTRL-C子进程?

时间:2022-10-20 22:42:19
啊,真是被这个问题整无奈了。

问题描述:通过一个进程(CUI/GUI)向另一个CUI进程发送CTRL-C,使后者结束执行。

基本要求:1 CUI子进程无法更改,如PING。
          2 父进程可以是GUI,也可以是CUI进程。
          3 保证子进程有机会在结束前保存其数据。一般CUI应该对用户点击关闭按钮和CTRL-C这2种关闭方式有对应的处理,以保证其数据可以被保存。这第三条的要求其实就是说实现的方式不要比CTRL-C和点击关闭按钮来得更暴力就可以。


目前花了近30个小时,查看各类文档,试验各种技术,代码组合,仅一种方式可以实现:

	
TCHAR cmdline[] = _T("ping 127.0.0.1 -t");
BOOL b = CreateProcess(NULL,cmdline,NULL,NULL,FALSE,NULL,NULL,NULL,&si,&pi);
if (!b)
{
printf("CreateProcess failed!\n");
return -1;
}
SetConsoleCtrlHandler(CtrlHandler,TRUE);

这样子可以做到CTRL-C的时候结束PING子进程。但是离我要求的发送CTRL-C还有距离。

下面的是另外的实现,这种情况很恶心,拜其所赐,30多小时毫无进展。
	TCHAR cmdline[] = _T("ping 127.0.0.1 -t");
BOOL b = CreateProcess(NULL,cmdline,NULL,NULL,FALSE,CREATE_NEW_PROCESS_GROUP,NULL,NULL,&si,&pi);
if (!b)
{
printf("CreateProcess failed!\n");
return -1;
}
SetConsoleCtrlHandler(CtrlHandler,TRUE);
//中间略去一些无关紧要的代码
GenerateConsoleCtrlEvent(CTRL_C_EVENT,pi.dwProcessId);

无论怎么发送CTRL-C或者是你手动CTRL-C,PING都不会理会(这就是其恶心之处,在同一个CONSOLE下,PING子程序用着通用的ctrl handler居然不处理CTRL-C)

上面就是大概的描述,请问有知道此问题如何解决的吗?


最后附上我的测试代码方便大家试验(用了预编译头,你可能需要更改下头文件的引用)
#include "stdafx.h"
#include <Windows.h>
#include <process.h>

PROCESS_INFORMATION pi;

BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_C_EVENT:
printf("Pid=%d",_getpid());
return TRUE;
case CTRL_BREAK_EVENT:
printf("server Break Event\n");
printf("%s",GenerateConsoleCtrlEvent(CTRL_C_EVENT,pi.dwProcessId)?"true\n":"false\n");
return TRUE;
default:
return FALSE;
}
}

int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
ZeroMemory(&pi,sizeof(pi));
si.cb = sizeof(si);
TCHAR cmdline[] = _T("ping 127.0.0.1 -t");
BOOL b = CreateProcess(NULL,cmdline,NULL,NULL,FALSE,CREATE_NEW_PROCESS_GROUP,NULL,NULL,&si,&pi);
if (!b)
{
printf("CreateProcess failed!\n");
return -1;
}
SetConsoleCtrlHandler(CtrlHandler,TRUE);

int i = 0;
while(1)
{
scanf("%d",&i);
if (i==1)
{
ZeroMemory(&pi,sizeof(pi));
b = CreateProcess(NULL,cmdline,NULL,NULL,FALSE,NULL/*CREATE_NEW_PROCESS_GROUP*/,NULL,NULL,&si,&pi);
}
if (i>1)
{
//GenerateConsoleCtrlEvent(CTRL_C_EVENT,pi.dwProcessId);
printf("%s",GenerateConsoleCtrlEvent(CTRL_C_EVENT,i)?"true\n":"false\n");
}
if(WaitForSingleObject(pi.hProcess,200)==WAIT_OBJECT_0)
printf("Ping exited!");
// break;
}
return 0;
}

11 个解决方案

#1


纯属路过 ···  不懂

#2


一个CUI程序我目前知晓的有3中方式获得其CONSOLE窗口:
1、系统为其分配,父窗口没CONSOLE和CreateProcess指定CREATE_NEW_CONSOLE参数这2种情况吧。
2、继承父进程的CONSOLE,在不指定CREATE_NEW_CONSOLE的情况下,会继承父进程的CONSOLE(似乎跟CreateProcess中的继承相关参数没关系,我上面指定继承为FALSE它还是用父进程的CONSOLE,难道我这里猜错了?)
3、显示指定DETACH_PROCESS,然后自己AllocConsole或者AttachConsole。

其中第3种情况,需要子进程主动配合,所以用不了。 对于第一种情况,父窗口没CONSOLE的话,一般是GUI程序,系统为子CUI程序创建新的CONSOLE,这时虽有父子关系,但是CONSOLE上却没关系,有个概念是process group,MSDN里仅仅点到为止,GOOGLE了很多网页全部是UNIX/LINUX下的,WINDOWS下的process group一篇都没见过,不过有篇似乎脱离特定系统进行了讲解,我看了这些,虽然不同系统,不过概念上还是十分相近。套用其中一句说每个process都属于一个process group,这里的情况是gid未知...我也不知道如何去获取,底层接触不够。 

再说CREATE_NEW_CONSOLE这种情况,如果父进程没CONSOLE,跟上面讨论差不多,若父进程是CUI程序的话,这时桌面会出现2个CONSOLE,它们显然是不同的,CONSOLE窗口间没什么关系。

插入一点GenerateConsoleCtrlEvent的说明,这个函数带有2个参数,其一知道EVENT类型,只有CTRL_C_EVENT跟CTRL_BREAK_EVENT,另一个是上面提到的process group的标识符。函数的功能是向gid标识的进程组中所有的进程发送event,但是只有跟调用GenerateConsoleCtrlEvent的进程共用同一个CONSOLE的进程才可以收到消息。
再说CreateProcess时使用CREATE_NEW_PROCESS_GROUP的情况,指定这个参数可以产生一个process group,其gid跟产生出的子进程pid相同。这样在继承父窗口的情形下,满足了使用GenerateConsoleCtrlEvent的2个条件:1.已知一个gid;2.目标进程(子进程)与调用进程(父进程)共用CONSOLE。这就是我上面的程序代码里表达的情形,但是无法得到我想要的,这是个问题。

上面写了我在处理时所考虑的,有什么地方想得不对的吗?请指出,谢谢!

PS:windows下的process group真的是什么资料都没有的么?MSDN里居然有篇processor group,当时由于看错还激动了个好几十秒。

#3


BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {
    case CTRL_C_EVENT:
        printf("Pid=%d",_getpid());
        return TRUE;
    case CTRL_BREAK_EVENT:
        printf("server Break Event\n");
        printf("%s",GenerateConsoleCtrlEvent(CTRL_C_EVENT,pi.dwProcessId)?"true\n":"false\n");
        return TRUE;
    default:
        return FALSE;
    }
}

加上如下事件进行测试

case CTRL_C_EVENT: 
                    message = "A CTRL_C_EVENT was raised by the user."; 
                    break; 
                case CTRL_BREAK_EVENT: 
                    message = "A CTRL_BREAK_EVENT was raised by the user."; 
                    break; 
                case CTRL_CLOSE_EVENT: 
                    message = "A CTRL_CLOSE_EVENT was raised by the user."; 
                    break; 
                case CTRL_LOGOFF_EVENT: 
                    message = "A CTRL_LOGOFF_EVENT was raised by the user."; 
                    break; 
                case CTRL_SHUTDOWN_EVENT: 
                    message = "A CTRL_SHUTDOWN_EVENT was raised by the user."; 
                    break; 

#4


引用 3 楼 tr0j4n 的回复:
BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {
    case CTRL_C_EVENT:
        printf("Pid=%d",_getpid());
        return TRUE;
    case CTRL_BREAK_EVENT:
        pr……


感谢你的回复!

对于CLOSE,LOGOFF,SHUTDOWN由于无法使用GenerateConsoleCtrlEvent发送,所以没有做处理,而是转交给默认handler(好象是这个名字ExitProcess)处理。

我代码中pirntf的一些信息,一方面也是出于判断CtrlHandler是否被调用而写的。

#5


这个CtrlHandler是给我自己的程序用的,目的是在于忽略CTRL-C事件,不会导致关闭子进程是连自己也关闭了。

子进程创建之前没有对CONSOLE做什么事,相信此时CONSOLE还是默认的event handler(ExitProcess),不清楚子进程是否会有可能继承这个事件处理list。不过至少MSDN里明确说明,CONSOLE对CTRL-C的态度(忽略或是处理)是会被继承的(通过SetConsoleCtrlHandler(NULL,TRUE))。

#6


拜膜 3楼

#7


有人么?

我再一次把问题简化吧。。。

基本情况:
1.使用process group + GenerateConsoleCtrlEvent实现对子进程发生ctrl-c事件。
2.父进程和子进程共用一个console。
3.子进程代表了一个process group的leader。
4.父进程跟子进程的event handler都被设置成功了,而是都对CTRL-C做了处理:打印一行信息。
5.共用的console的INPUT buff mode设置了ENABLE_PROCESSED_INPUT。

此时,按下CTRL-C
父进程捕获到了CTRL-C,然后打印了一行文本,子进程确没有任何反应,为什么呢?

#8


向楼主学习!

#9


关注,楼主现在这个问题解决了吗?

#10


还没有解决,模拟不出来。不过直接重定向stdin,stdout,手动CTRL-C是可以的,但是如果只能手动的话那就没什么意义了。

或许只能试着在中间添加一个代理来解决,听说VS也是如此实现的,不过时间关系还没有实践。

#11


前些日子想写个GUI版的CMD,CreateProcess了一个cmd.exe,用管道重定向,但是一直没办法实现ctrl+c跟ctrl+break,后来一们外国大哥说用CreateRemoteThread把GenerateConsoleCtrlEvent注入到cmd.exe可以实现,但是只有ctrl+break有用,ctrl+c没反应,求解

#1


纯属路过 ···  不懂

#2


一个CUI程序我目前知晓的有3中方式获得其CONSOLE窗口:
1、系统为其分配,父窗口没CONSOLE和CreateProcess指定CREATE_NEW_CONSOLE参数这2种情况吧。
2、继承父进程的CONSOLE,在不指定CREATE_NEW_CONSOLE的情况下,会继承父进程的CONSOLE(似乎跟CreateProcess中的继承相关参数没关系,我上面指定继承为FALSE它还是用父进程的CONSOLE,难道我这里猜错了?)
3、显示指定DETACH_PROCESS,然后自己AllocConsole或者AttachConsole。

其中第3种情况,需要子进程主动配合,所以用不了。 对于第一种情况,父窗口没CONSOLE的话,一般是GUI程序,系统为子CUI程序创建新的CONSOLE,这时虽有父子关系,但是CONSOLE上却没关系,有个概念是process group,MSDN里仅仅点到为止,GOOGLE了很多网页全部是UNIX/LINUX下的,WINDOWS下的process group一篇都没见过,不过有篇似乎脱离特定系统进行了讲解,我看了这些,虽然不同系统,不过概念上还是十分相近。套用其中一句说每个process都属于一个process group,这里的情况是gid未知...我也不知道如何去获取,底层接触不够。 

再说CREATE_NEW_CONSOLE这种情况,如果父进程没CONSOLE,跟上面讨论差不多,若父进程是CUI程序的话,这时桌面会出现2个CONSOLE,它们显然是不同的,CONSOLE窗口间没什么关系。

插入一点GenerateConsoleCtrlEvent的说明,这个函数带有2个参数,其一知道EVENT类型,只有CTRL_C_EVENT跟CTRL_BREAK_EVENT,另一个是上面提到的process group的标识符。函数的功能是向gid标识的进程组中所有的进程发送event,但是只有跟调用GenerateConsoleCtrlEvent的进程共用同一个CONSOLE的进程才可以收到消息。
再说CreateProcess时使用CREATE_NEW_PROCESS_GROUP的情况,指定这个参数可以产生一个process group,其gid跟产生出的子进程pid相同。这样在继承父窗口的情形下,满足了使用GenerateConsoleCtrlEvent的2个条件:1.已知一个gid;2.目标进程(子进程)与调用进程(父进程)共用CONSOLE。这就是我上面的程序代码里表达的情形,但是无法得到我想要的,这是个问题。

上面写了我在处理时所考虑的,有什么地方想得不对的吗?请指出,谢谢!

PS:windows下的process group真的是什么资料都没有的么?MSDN里居然有篇processor group,当时由于看错还激动了个好几十秒。

#3


BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {
    case CTRL_C_EVENT:
        printf("Pid=%d",_getpid());
        return TRUE;
    case CTRL_BREAK_EVENT:
        printf("server Break Event\n");
        printf("%s",GenerateConsoleCtrlEvent(CTRL_C_EVENT,pi.dwProcessId)?"true\n":"false\n");
        return TRUE;
    default:
        return FALSE;
    }
}

加上如下事件进行测试

case CTRL_C_EVENT: 
                    message = "A CTRL_C_EVENT was raised by the user."; 
                    break; 
                case CTRL_BREAK_EVENT: 
                    message = "A CTRL_BREAK_EVENT was raised by the user."; 
                    break; 
                case CTRL_CLOSE_EVENT: 
                    message = "A CTRL_CLOSE_EVENT was raised by the user."; 
                    break; 
                case CTRL_LOGOFF_EVENT: 
                    message = "A CTRL_LOGOFF_EVENT was raised by the user."; 
                    break; 
                case CTRL_SHUTDOWN_EVENT: 
                    message = "A CTRL_SHUTDOWN_EVENT was raised by the user."; 
                    break; 

#4


引用 3 楼 tr0j4n 的回复:
BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {
    case CTRL_C_EVENT:
        printf("Pid=%d",_getpid());
        return TRUE;
    case CTRL_BREAK_EVENT:
        pr……


感谢你的回复!

对于CLOSE,LOGOFF,SHUTDOWN由于无法使用GenerateConsoleCtrlEvent发送,所以没有做处理,而是转交给默认handler(好象是这个名字ExitProcess)处理。

我代码中pirntf的一些信息,一方面也是出于判断CtrlHandler是否被调用而写的。

#5


这个CtrlHandler是给我自己的程序用的,目的是在于忽略CTRL-C事件,不会导致关闭子进程是连自己也关闭了。

子进程创建之前没有对CONSOLE做什么事,相信此时CONSOLE还是默认的event handler(ExitProcess),不清楚子进程是否会有可能继承这个事件处理list。不过至少MSDN里明确说明,CONSOLE对CTRL-C的态度(忽略或是处理)是会被继承的(通过SetConsoleCtrlHandler(NULL,TRUE))。

#6


拜膜 3楼

#7


有人么?

我再一次把问题简化吧。。。

基本情况:
1.使用process group + GenerateConsoleCtrlEvent实现对子进程发生ctrl-c事件。
2.父进程和子进程共用一个console。
3.子进程代表了一个process group的leader。
4.父进程跟子进程的event handler都被设置成功了,而是都对CTRL-C做了处理:打印一行信息。
5.共用的console的INPUT buff mode设置了ENABLE_PROCESSED_INPUT。

此时,按下CTRL-C
父进程捕获到了CTRL-C,然后打印了一行文本,子进程确没有任何反应,为什么呢?

#8


向楼主学习!

#9


关注,楼主现在这个问题解决了吗?

#10


还没有解决,模拟不出来。不过直接重定向stdin,stdout,手动CTRL-C是可以的,但是如果只能手动的话那就没什么意义了。

或许只能试着在中间添加一个代理来解决,听说VS也是如此实现的,不过时间关系还没有实践。

#11


前些日子想写个GUI版的CMD,CreateProcess了一个cmd.exe,用管道重定向,但是一直没办法实现ctrl+c跟ctrl+break,后来一们外国大哥说用CreateRemoteThread把GenerateConsoleCtrlEvent注入到cmd.exe可以实现,但是只有ctrl+break有用,ctrl+c没反应,求解