简体   繁体   中英

C++ WINAPI: How to kill child processes when the calling (parent) process is forcefully terminated?

Could anyone kindly tell me how to kill child processes when the calling (parent) process is forcefully terminated? By the way, I am not able to change the source code of child application.

I have checked the existing thread in StackOverflow and it seems JobObject is the correct way to do so. However when I tested it (using a console application to call notepad.exe), I found that when the console application exited, Notepad didn't.

I used CreateProcess to spawn the new process.

I have also seen somebody says that setting up a pipe between parent process and child process will do the job, but I have not yet tried it.

If anyone can give me some hints, I would really appreciate it.

Update: WINAPI AssignProcessToJobObject failed to work if without | CREATE_BREAKAWAY_FROM_JOB | CREATE_BREAKAWAY_FROM_JOB in CreatProcess . Now it works!

Thank everyone.

Update again:

It's really tricky. I am always confused by Windows API.

Group 1:

Process flag: CREATE_SUSPENDED

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_SECURITY_RESTRICTED_TOKEN or JOB_OBJECT_SECURITY_NO_ADMIN or JOB_OBJECT_LIMIT_BREAKAWAY_OK JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_SECURITY_RESTRICTED_TOKEN or JOB_OBJECT_SECURITY_NO_ADMIN or JOB_OBJECT_LIMIT_BREAKAWAY_OK

Result: AssingProcessToJobObject fails with Error code 5 Access is denied

Group 2:

Process flag: CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_SECURITY_RESTRICTED_TOKEN or JOB_OBJECT_SECURITY_NO_ADMIN JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_SECURITY_RESTRICTED_TOKEN or JOB_OBJECT_SECURITY_NO_ADMIN

Results: AssingProcessToJobObject succeeds, but child process fails to be killed when parent one is killed.

Group 3:

Process flag: CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE

Results: AssingProcessToJobObject succeeds, and child process is automatically killed when parent one is killed.

Group 4:

Process flag: CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB

JobObject flag: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK

Results: same to Group 3.

The following code uses JobObeject which I copied from http://cboard.cprogramming.com/windows-programming/60561-program-termination.html#post430075

#define _WIN32_WINNT 0x0500
#include <windows.h>

int main(void)
{
    HANDLE                               hJob;
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
    PROCESS_INFORMATION                  pi   = { 0 };
    STARTUPINFO                          si   = { 0 };


    /*
     * Create a job object.
     */
    hJob = CreateJobObject(NULL, NULL);

    /*
     * Causes all processes associated with the job to terminate when the
     * last handle to the job is closed.
     */
    jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));

    /*
     * Create the process suspended.
     */
    si.cb = sizeof(si);
    CreateProcess(TEXT("C:\\Windows\\System32\\Notepad.exe"), NULL, NULL, NULL, FALSE, 
                  CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB /*Important*/, NULL, NULL, &si, &pi);

    /*
     * Add the process to our job object.
     */
    AssignProcessToJobObject(hJob, pi.hProcess); // Does not work if without CREATE_BREAKAWAY_FROM_JOB


    /*
     * Start our suspended process.
     */
    ResumeThread(pi.hThread);

    /*
     * At this point, if we are closed, windows will automatically clean up
     * by closing any handles we have open. When the handle to the job object
     * is closed, any processes belonging to the job will be terminated.
     * Note: Grandchild processes automatically become part of the job and
     * will also be terminated. This behaviour can be avoided by using the
     * JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK limit flag.
     */

    /*
     * Do what you like here. For demonstration purposes we will just wait
     * for the child process to complete. Click our close button to see
     * everything in action.
     */
    WaitForSingleObject(pi.hProcess, 3000);
    /*
     * Cleanup. As mentioned, Windows does this automagically when our process
     * exits, but it is good style to do it explicitly. 
     */
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    CloseHandle(hJob);

    return 0;
}

Do NOT test from within Visual Studio, via F5 or Ctrl+F5. When Visual Studio launch your program, it itself uses Job to manage things, and that interacts badly with your code.

Open a console and launch your exe "manually". Your code is correct ("works here", with VS2010 on Seven)

Edit: you may add error checking, don't assume all APIs always succeed.

Edit: you may use the IsProcessInJob API to know whether your process is already in a job when starting. If this is the case, child processes, by default, are created in that preexisting job, and you then have to use CREATE_BREAKAWAY_FROM_JOB (if you don't, you can't use AssignProcessToJobObject )

When starting the process from Visual Studio, or by double click in the Explorer,the process is launched in a job.

Adding, my code based on yours, works from VS or Explorer.

#include <Windows.h>
#include <stdio.h>

int main( void ) {

    BOOL bIsProcessInJob;
    BOOL bSuccess = IsProcessInJob( GetCurrentProcess(), NULL, &bIsProcessInJob );
    if ( bSuccess == 0 ) {
        printf( "IsProcessInJob failed: error %d\n", GetLastError() );
        return 0;
    }
    if ( bIsProcessInJob ) {
        MessageBox( NULL, L"Process is already in Job", L"Job Test", 0 );
    }

    HANDLE hJob = CreateJobObject( NULL, NULL );
    if ( hJob == NULL ) {
        printf( "CreateJobObject failed: error %d\n", GetLastError() );
        return 0;
    }

    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
    jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    bSuccess = SetInformationJobObject( hJob, JobObjectExtendedLimitInformation, &jeli, sizeof( jeli ) );
    if ( bSuccess == 0 ) {
        printf( "SetInformationJobObject failed: error %d\n", GetLastError() );
        return 0;
    }

    PROCESS_INFORMATION pi = { 0 };
    STARTUPINFO si = { 0 };
    si.cb = sizeof( si );
    DWORD dwCreationFlags = bIsProcessInJob ? CREATE_BREAKAWAY_FROM_JOB : 0;
    bSuccess = CreateProcess( L"C:\\Windows\\System32\\Notepad.exe", NULL, NULL, NULL, FALSE, 
                              dwCreationFlags, NULL, NULL, &si, &pi);
    if ( bSuccess == 0 ) {
        printf( "CreateProcess failed: error %d\n", GetLastError() );
        return 0;
    }

    bSuccess = AssignProcessToJobObject( hJob, pi.hProcess );
    if ( bSuccess == 0 ) {
        printf( "AssignProcessToJobObject failed: error %d\n", GetLastError() );
        return 0;
    }

    CloseHandle( pi.hThread );
    CloseHandle( pi.hProcess );

    printf( "Type a key to exit..." );
    getchar();

    CloseHandle( hJob );

    return 0;

}

To summerize my comments:

The process that spawns the child have to create the job and is the only that has an open handle to it. All spwaned child processes are then part of the job.

hJob = CreateJobObject(NULL, NULL);

JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {
    0, 0, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, 0
};
SetInformationJobObject(hJob, &jeli);

if (AssignProcessToJobObject(hJob, GetCurrentProcess()) == FALSE) {
    DWORD Error = GetLastError();
}

If the above assignment fails, try to play with security flags like JOB_OBJECT_SECURITY_NO_ADMIN or JOB_OBJECT_SECURITY_RESTRICTED_TOKEN .

MSDN about AssignProcessToJobObject :

If the process is already running and the job has security limitations, AssignProcessToJobObject may fail. For example, if the primary token of the process contains the local administrators group, but the job object has the security limitation JOB_OBJECT_SECURITY_NO_ADMIN, the function fails. If the job has the security limitation JOB_OBJECT_SECURITY_ONLY_TOKEN, the process must be created suspended. To create a suspended process, call the CreateProcess function with the CREATE_SUSPENDED flag.

A process can be associated only with a single job. A process inherits limits from the job it is associated with and adds its accounting information to the job. If a process is associated with a job, all processes it creates are associated with that job by default. To create a process that is not part of the same job, call the CreateProcess function with the CREATE_BREAKAWAY_FROM_JOB flag.

So child processes are automatically part of the job in this case. However in order to create a job, it could be necessary to start the console application with higher rights. What is the error code returned after call to AssignProcessToJobObject(hJob, GetCurrentProcess()) ?

MSDN about CREATE_BREAKAWAY_FROM_JOB :

The child processes of a process associated with a job are not associated with the job.

If the calling process is not associated with a job, this constant has no effect. If the calling process is associated with a job, the job must set the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit.

The flag CREATE_BREAKAWAY_FROM_JOB should prevent that the child process is part of the job. But this only applies if JOB_OBJECT_LIMIT_BREAKAWAY_OK was setfor the job.

Just a thought, if the intention is just to kill child processes when Parent Process is killed:

What if keep the child process handles and terminate them during the OnClose(or whenever needed) of the parent Process. Something like this:

 // Creating
 UINT u = 0;
 HANDLE* pH = new HANDLE[2];
 for (int i =0 ; i< 2; i++)
 {
    STARTUPINFO si ;
    ZeroMemory(&si, sizeof(si));
    si.wShowWindow = SW_SHOW;
    si.cb = sizeof(si);
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    if (!CreateProcess(TEXT("C:\\Windows\\System32\\Notepad.exe"), NULL, NULL, NULL, FALSE, 
                   NULL, NULL, NULL, &si, &pi))
                   OutputDebugString(_T("Failed"));
    else
    OutputDebugString(_T("Success"));

    pH[i] = pi.hProcess;
  }

// Killing children
i = 0;
while (i<2)
{
::TerminateProcess(pH[i], u);
i++;
}

Rather than terminate process, send message could also be after enumerating the window handles. Let me know if I am anything is missing.

I have a somewhat similar situation. My main application creates a child process that is used to log "events". The child process keeps a record of its parent processes (it can have many). Using a timer in the child process, I am able to check if the parent process has crashed, or, shut down. When this is detected, the child process shuts down cleanly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM