71

在 C++ Windows 应用程序中,我启动了几个长时间运行的子进程(目前我使用 CreateProcess(...) 来执行此操作。

如果我的主进程崩溃或关闭,我希望子进程自动关闭。

由于要求这需要对“父级”崩溃起作用,我相信这需要使用操作系统的某些 API/功能来完成。以便清理所有“子”进程。

我该怎么做呢?

4

7 回答 7

84

Windows API 支持称为“作业对象”的对象。以下代码将创建一个“作业”,该“作业”被配置为在主应用程序结束时(当其句柄被清理时)关闭所有进程。此代码应该只运行一次。:

HANDLE ghJob = CreateJobObject( NULL, NULL); // GLOBAL
if( ghJob == NULL)
{
    ::MessageBox( 0, "Could not create job object", "TEST", MB_OK);
}
else
{
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };

    // Configure all child processes associated with the job to terminate when the
    jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    if( 0 == SetInformationJobObject( ghJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)))
    {
        ::MessageBox( 0, "Could not SetInformationJobObject", "TEST", MB_OK);
    }
}

然后在创建每个子进程时,执行以下代码来启动每个子进程并将其添加到作业对象中:

STARTUPINFO info={sizeof(info)};
PROCESS_INFORMATION processInfo;

// Launch child process - example is notepad.exe
if (::CreateProcess( NULL, "notepad.exe", NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo))
{
    ::MessageBox( 0, "CreateProcess succeeded.", "TEST", MB_OK);
    if(ghJob)
    {
        if(0 == AssignProcessToJobObject( ghJob, processInfo.hProcess))
        {
            ::MessageBox( 0, "Could not AssignProcessToObject", "TEST", MB_OK);
        }
    }

    // Can we free handles now? Not sure about this.
    //CloseHandle(processInfo.hProcess); 
    CloseHandle(processInfo.hThread);
}

VISTA 注意:如果您在 vista 上使用 AssignProcessToObject() 遇到访问被拒绝问题,请参阅AssignProcessToJobObject 在 Vista 上总是返回“访问被拒绝” 。

于 2008-09-10T00:22:49.550 回答
5

一个有点骇人听闻的解决方案是让父进程作为调试器附加到每个子进程(使用DebugActiveProcess)。当调试器终止时,它的所有被调试进程也会终止。

更好的解决方案(假设您也编写了子进程)是让子进程监视父进程并在它消失时退出。

于 2008-09-10T00:28:13.920 回答
3

Windows Job Objects 听起来是个不错的起点。作业对象的名称必须是众所周知的,或者传递给子对象(或继承句柄)。当父母死亡时,孩子需要得到通知,无论是通过失败的 IPC“心跳”还是只是父母进程句柄上的 WFMO/WFSO。那时任何子进程都可以使用 TermianteJobObject 来关闭整个组。

于 2008-09-10T06:24:54.967 回答
1

您可以保持单独的看门狗进程运行。它的唯一任务是观察当前进程空间以发现您描述的情况。它甚至可以在崩溃后重新启动原始应用程序或为用户提供不同的选项、收集调试信息等。只要尽量保持简单,这样您就不需要第二个看门狗来观看第一个看门狗。

于 2008-09-10T00:25:40.853 回答
0

您可以在创建进程之前将作业分配给父进程:

static HANDLE hjob_kill_on_job_close=INVALID_HANDLE_VALUE;
void init(){
    hjob_kill_on_job_close = CreateJobObject(NULL, NULL);
    if (hjob_kill_on_job_close){
        JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobli = { 0 };
        jobli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
        SetInformationJobObject(hjob_kill_on_job_close,
            JobObjectExtendedLimitInformation,
            &jobli, sizeof(jobli));
        AssignProcessToJobObject(hjob_kill_on_job_close, GetCurrentProcess());
    }
}
void deinit(){
    if (hjob_kill_on_job_close) {
        CloseHandle(hjob_kill_on_job_close);
    }
}

JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE当作业的最后一个句柄关闭时,导致与作业关联的所有进程终止。默认情况下,所有子进程都会自动分配给作业,除非您CREATE_BREAKAWAY_FROM_JOB在调用CreateProcess. 有关. _ _CREATE_BREAKAWAY_FROM_JOB

您可以使用 Sysinternals 的流程资源管理器来确保将所有流程分配给作业。像这样: 在此处输入图像描述

于 2021-09-19T12:54:29.870 回答
-2

您可能必须保留您启动的进程的列表,并在退出程序时将它们一一终止。我不确定在 C++ 中执行此操作的具体细节,但应该不难。困难的部分可能是确保在应用程序崩溃的情况下关闭子进程。.Net 能够添加一个函数,当发生未处理的异常时调用该函数。我不确定 C++ 是否提供相同的功能。

于 2008-09-10T00:19:50.493 回答
-2

您可以将每个进程封装在一个 C++ 对象中,并将它们的列表保存在全局范围内。析构函数可以关闭每个进程。如果程序正常退出但它崩溃了,这将正常工作,所有赌注都关闭了。

这是一个粗略的例子:

class myprocess
{
public:
    myprocess(HANDLE hProcess)
        : _hProcess(hProcess)
    { }

    ~myprocess()
    {
        TerminateProcess(_hProcess, 0);
    }

private:
    HANDLE _hProcess;
};

std::list<myprocess> allprocesses;

然后每当你启动一个,调用 allprocessess.push_back(hProcess);

于 2008-09-10T00:34:11.003 回答