关于c#:杀死父进程时杀死子进程

Kill child process when parent process is killed

我正在使用我的应用程序中的System.Diagnostics.Process类创建新进程。 当/如果我的应用程序崩溃了,我希望这个进程被杀死。 但是如果我从任务管理器中删除我的应用程序,子进程就不会被杀死。 有没有办法让子进程依赖于父进程?


从这个论坛,归功于'Josh'。

Application.Quit()Process.Kill()是可能的解决方案,但已被证明是不可靠的。当您的主应用程序死亡时,您仍然会继续运行子进程。我们真正想要的是,一旦主要过程消失,子进程就会死亡。

解决方案是使用"作业对象"http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx。

我们的想法是为您的主应用程序创建一个"作业对象",并使用作业对象注册您的子进程。如果主进程终止,操作系统将负责终止子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

看着构造函数......

1
2
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;

这里的关键是正确设置作业对象。在构造函数中,我将"limits"设置为0x2000,这是JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE的数值。

MSDN将此标志定义为:

Causes all processes associated with
the job to terminate when the last
handle to the job is closed.

一旦设置了这个类......你只需要在每个子进程中注册该作业。例如:

1
2
3
4
5
6
7
8
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Excel.Application app = new Excel.ApplicationClass();

uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
 job.AddProcess(Process.GetProcessById((int)pid).Handle);


本文旨在作为@Matt Howells答案的扩展,专门针对那些在Vista或Win7下使用Job Objects时遇到问题的人,特别是在调用AssignProcessToJobObject时遇到访问被拒绝错误('5')。

TL;博士

要确保与Vista和Win7的兼容性,请将以下清单添加到.NET父进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8" standalone="yes"?>

  <v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
    <v3:security>
      <v3:requestedPrivileges>
        <v3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </v3:requestedPrivileges>
    </v3:security>
  </v3:trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <!-- We specify these, in addition to the UAC above, so we avoid Program Compatibility Assistant in Vista and Win7 -->
    <!-- We try to avoid PCA so we can use Windows Job Objects -->
    <!-- See https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed -->

   
      <!--The ID below indicates application support for Windows Vista -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!--The ID below indicates application support for Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
    </application>
  </compatibility>
</assembly>

请注意,在Visual Studio 2012中添加新清单时,它将包含上面的代码段,因此您无需从侦听中复制它。它还将包含Windows 8的节点。

完整的解释

如果您启动的进程已与另一个作??业关联,则您的作业关联将失败并显示拒绝访问错误。进入程序兼容性助手,从Windows Vista开始,将所有类型的进程分配给自己的作业。

在Vista中,您可以通过简单地包含应用程序清单来标记您的应用程序被排除在PCA之外。 Visual Studio似乎会自动为.NET应用程序执行此操作,因此您可以在那里使用。

一个简单的清单不再在Win7中削减它。 [1]在那里,您必须明确指定您与清单中的标记兼容Win7。 [2]

这让我担心Windows 8.我是否必须再次更改我的清单?显然云中断了,因为Windows 8现在允许进程属于多个作业。 [3]所以我还没有测试过它,但我想如果你只是包含一个带有支持的OS信息的清单,那么这种疯狂现在就会结束。

提示1:如果您正在使用Visual Studio开发.NET应用程序,那么[4]是关于如何自定义应用程序清单的一些很好的说明。

技巧2:小心从Visual Studio启动应用程序。我发现,在添加适当的清单后,从Visual Studio启动时我仍然遇到PCA问题,即使我使用Start而不调试。但是,从资源管理器启动我的应用程序。使用注册表手动添加devenv以从PCA中排除后,启动使用VS中的Job Objects的应用程序也开始工作。 [5]

提示3:如果您想知道PCA是否是您的问题,请尝试从命令行启动应用程序,或将程序复制到网络驱动器并从那里运行。 PCA在这些上下文中自动禁用。

[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an卸载器通吃2 - 因为,我们改变的,在规则上,you.aspx

[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant

[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx:
"一个进程可以与Windows 8中的多个作业相关联"

[4]如何使用VS2008将应用程序清单嵌入到应用程序中?

[5]如何停止Visual Studio调试器在作业对象中启动我的进程?


这个答案始于@Matt Howells的优秀答案和其他答案(请参阅下面代码中的链接)。改进:

  • 支持32位和64位。
  • 解决了@Matt Howells的回答中的一些问题:

  • extendedInfoPtr的小内存泄漏
  • 'Win32'编译错误,和
  • 我在调用CreateJobObject时遇到的堆栈不平衡异常(使用Windows 10,Visual Studio 2015,32位)。
  • 命名作业,例如,如果您使用SysInternals,则可以轻松找到它。
  • 有一个更简单的API和更少的代码。

以下是使用此代码的方法:

1
2
3
4
// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);

要支持Windows 7,需要:

  • 一个简单的app.manifest更改为@adam smith描述。
  • 如果您使用的是Visual Studio,则添加注册表设置。

在我的情况下,我不需要支持Windows 7,所以我有一个简单的检查
下面的静态构造函数的顶部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/// <summary>
/// Allows processes to be automatically killed if this parent process unexpectedly quits.
/// This feature requires Windows 8 or greater. On Windows 7, nothing is done.</summary>
/// <remarks>References:
///  https://stackoverflow.com/a/4657392/386091
///  https://stackoverflow.com/a/9164742/386091 </remarks>
public static class ChildProcessTracker
{
    /// <summary>
    /// Add the process to be tracked. If our current process is killed, the child processes
    /// that we are tracking will be automatically killed, too. If the child process terminates
    /// first, that's fine, too.</summary>
    /// <param name="process"></param>
    public static void AddProcess(Process process)
    {
        if (s_jobHandle != IntPtr.Zero)
        {
            bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
            if (!success && !process.HasExited)
                throw new Win32Exception();
        }
    }

    static ChildProcessTracker()
    {
        // This feature requires Windows 8 or later. To support Windows 7 requires
        //  registry settings to be added if you are using Visual Studio plus an
        //  app.manifest change.
        //  https://stackoverflow.com/a/4232259/386091
        //  https://stackoverflow.com/a/9507862/386091
        if (Environment.OSVersion.Version < new Version(6, 2))
            return;

        // The job name is optional (and can be null) but it helps with diagnostics.
        //  If it's not null, it has to be unique. Use SysInternals' Handle command-line
        //  utility: handle -a ChildProcessTracker
        string jobName ="ChildProcessTracker" + Process.GetCurrentProcess().Id;
        s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);

        var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();

        // This is the key flag. When our process is killed, Windows will automatically
        //  close the job handle, and when that happens, we want the child processes to
        //  be killed, too.
        info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

        var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        try
        {
            Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

            if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
                extendedInfoPtr, (uint)length))
            {
                throw new Win32Exception();
            }
        }
        finally
        {
            Marshal.FreeHGlobal(extendedInfoPtr);
        }
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
        IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    // Windows will automatically close any open job handles when our process terminates.
    //  This can be verified by using SysInternals' Handle utility. When the job handle
    //  is closed, the child processes will be killed.
    private static readonly IntPtr s_jobHandle;
}

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public JOBOBJECTLIMIT LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public UInt32 ActiveProcessLimit;
    public Int64 Affinity;
    public UInt32 PriorityClass;
    public UInt32 SchedulingClass;
}

[Flags]
public enum JOBOBJECTLIMIT : uint
{
    JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}

[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UIntPtr ProcessMemoryLimit;
    public UIntPtr JobMemoryLimit;
    public UIntPtr PeakProcessMemoryUsed;
    public UIntPtr PeakJobMemoryUsed;
}

我通过以编程方式比较托管版本和本机版本(整体大小以及每个成员的偏移量)仔细测试了32位和64位版本的结构。

我在Windows 7,8和10上测试了这段代码。


当您控制子进程运行的代码时,这可能适用于某些人。这种方法的好处是它不需要任何本机Windows调用。

基本思想是将子标准输入重定向到另一端连接到父级的流,并使用该流检测父级何时消失。当您使用System.Diagnostics.Process启动子项时,很容易确保其标准输入被重定向:

1
2
3
4
5
Process childProcess = new Process();
childProcess.StartInfo = new ProcessStartInfo("pathToConsoleModeApp.exe");
childProcess.StartInfo.RedirectStandardInput = true;

childProcess.StartInfo.CreateNoWindow = true; // no sense showing an empty black console window which the user can't input into

然后,在子进程上,利用以下事实:标准输入流中的Read s将始终以至少1个字节返回,直到流关闭,此时它们将开始返回0个字节。我最终做到这一点的方式概述如下;我的方式也使用消息泵来保持主线程可用于除了观看标准之外的其他东西,但是这种通用方法也可以在没有消息泵的情况下使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;

static int Main()
{
    Application.Run(new MyApplicationContext());
    return 0;
}

public class MyApplicationContext : ApplicationContext
{
    private SynchronizationContext _mainThreadMessageQueue = null;
    private Stream _stdInput;

    public MyApplicationContext()
    {
        _stdInput = Console.OpenStandardInput();

        // feel free to use a better way to post to the message loop from here if you know one ;)    
        System.Windows.Forms.Timer handoffToMessageLoopTimer = new System.Windows.Forms.Timer();
        handoffToMessageLoopTimer.Interval = 1;
        handoffToMessageLoopTimer.Tick += new EventHandler((obj, eArgs) => { PostMessageLoopInitialization(handoffToMessageLoopTimer); });
        handoffToMessageLoopTimer.Start();
    }

    private void PostMessageLoopInitialization(System.Windows.Forms.Timer t)
    {
        if (_mainThreadMessageQueue == null)
        {
            t.Stop();
            _mainThreadMessageQueue = SynchronizationContext.Current;
        }

        // constantly monitor standard input on a background thread that will
        // signal the main thread when stuff happens.
        BeginMonitoringStdIn(null);

        // start up your application's real work here
    }

    private void BeginMonitoringStdIn(object state)
    {
        if (SynchronizationContext.Current == _mainThreadMessageQueue)
        {
            // we're already running on the main thread - proceed.
            var buffer = new byte[128];

            _stdInput.BeginRead(buffer, 0, buffer.Length, (asyncResult) =>
                {
                    int amtRead = _stdInput.EndRead(asyncResult);

                    if (amtRead == 0)
                    {
                        _mainThreadMessageQueue.Post(new SendOrPostCallback(ApplicationTeardown), null);
                    }
                    else
                    {
                        BeginMonitoringStdIn(null);
                    }
                }, null);
        }
        else
        {
            // not invoked from the main thread - dispatch another call to this method on the main thread and return
            _mainThreadMessageQueue.Post(new SendOrPostCallback(BeginMonitoringStdIn), null);
        }
    }

    private void ApplicationTeardown(object state)
    {
        // tear down your application gracefully here
        _stdInput.Close();

        this.ExitThread();
    }
}

注意这种方法:

  • 启动的实际子.exe必须是一个控制台应用程序,因此它仍然附加到stdin / out / err。如上例所示,我通过创建一个引用现有项目的小型控制台项目,实例化我的应用程序上下文并在其中调用Application.Run(),轻松地调整了我现有的使用消息泵(但没有显示GUI)的应用程序。 Main控制台.exe的方法。

  • 从技术上讲,这只是在父进程退出时发出子进程的信号,因此无论父进程是正常退出还是崩溃,它都会起作用,但它仍然由子进程执行自己的关闭。这可能是也可能不是你想要的......


  • 一种方法是将父进程的PID传递给子进程。如果具有指定pid的进程存在,则子进程将定期轮询。如果不是它就会退出。

    您还可以在子方法中使用Process.WaitForExit方法在父进程结束时收到通知,但在任务管理器的情况下可能不起作用。


    还有另一种相关方法,简单有效,可以在程序终止时完成子进程。您可以从父级实现调试器并将其附加到它们;当父进程结束时,子进程将被操作系统杀死。它可以通过两种方式将调试器附加到子节点的父节点(请注意,您一次只能附加一个调试器)。您可以在此处找到有关此主题的更多信息。

    这里有一个实用程序类,它启动一个新进程并为其附加一个调试器。 Roger Knapp从这篇文章改编而来。唯一的要求是两个进程需要共享相同的位数。您无法从64位进程调试32位进程,反之亦然。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    public class ProcessRunner
    {
        #region"API imports"

        private const int DBG_CONTINUE = 0x00010002;
        private const int DBG_EXCEPTION_NOT_HANDLED = unchecked((int) 0x80010001);

        private enum DebugEventType : int
        {
            CREATE_PROCESS_DEBUG_EVENT = 3,
            //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
            CREATE_THREAD_DEBUG_EVENT = 2,
            //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
            EXCEPTION_DEBUG_EVENT = 1,
            //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
            EXIT_PROCESS_DEBUG_EVENT = 5,
            //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
            EXIT_THREAD_DEBUG_EVENT = 4,
            //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
            LOAD_DLL_DEBUG_EVENT = 6,
            //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
            OUTPUT_DEBUG_STRING_EVENT = 8,
            //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
            RIP_EVENT = 9,
            //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
            UNLOAD_DLL_DEBUG_EVENT = 7,
            //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct DEBUG_EVENT
        {
            [MarshalAs(UnmanagedType.I4)] public DebugEventType dwDebugEventCode;
            public int dwProcessId;
            public int dwThreadId;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public byte[] bytes;
        }

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern bool DebugActiveProcess(int dwProcessId);

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus);

        [DllImport("Kernel32.dll", SetLastError = true)]
        public static extern bool IsDebuggerPresent();

        #endregion

        public Process ChildProcess { get; set; }

        public bool StartProcess(string fileName)
        {
            var processStartInfo = new ProcessStartInfo(fileName)
            {
                UseShellExecute = false,
                WindowStyle = ProcessWindowStyle.Normal,
                ErrorDialog = false
            };

            this.ChildProcess = Process.Start(processStartInfo);
            if (ChildProcess == null)
                return false;

            new Thread(NullDebugger) {IsBackground = true}.Start(ChildProcess.Id);
            return true;
        }

        private void NullDebugger(object arg)
        {
            // Attach to the process we provided the thread as an argument
            if (DebugActiveProcess((int) arg))
            {
                var debugEvent = new DEBUG_EVENT {bytes = new byte[1024]};
                while (!this.ChildProcess.HasExited)
                {
                    if (WaitForDebugEvent(out debugEvent, 1000))
                    {
                        // return DBG_CONTINUE for all events but the exception type
                        var continueFlag = DBG_CONTINUE;
                        if (debugEvent.dwDebugEventCode == DebugEventType.EXCEPTION_DEBUG_EVENT)
                            continueFlag = DBG_EXCEPTION_NOT_HANDLED;
                        ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueFlag);
                    }
                }
            }
            else
            {
                //we were not able to attach the debugger
                //do the processes have the same bitness?
                //throw ApplicationException("Unable to attach debugger") // Kill child? // Send Event? // Ignore?
            }
        }
    }

    用法:

    1
        new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");


    我正在寻找一个不需要非托管代码的问题的解决方案。我也无法使用标准输入/输出重定向,因为它是Windows窗体应用程序。

    我的解决方案是在父进程中创建一个命名管道,然后将子进程连接到同一个管道。如果父进程退出,则管道被破坏,子进程可以检测到这一点。

    以下是使用两个控制台应用程序的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private const string PipeName ="471450d6-70db-49dc-94af-09d3f3eba529";

    public static void Main(string[] args)
    {
        Console.WriteLine("Main program running");

        using (NamedPipeServerStream pipe = new NamedPipeServerStream(PipeName, PipeDirection.Out))
        {
            Process.Start("child.exe");

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }

    儿童

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    private const string PipeName ="471450d6-70db-49dc-94af-09d3f3eba529"; // same as parent

    public static void Main(string[] args)
    {
        Console.WriteLine("Child process running");

        using (NamedPipeClientStream pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.In))
        {
            pipe.Connect();
            pipe.BeginRead(new byte[1], 0, 1, PipeBrokenCallback, pipe);

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }

    private static void PipeBrokenCallback(IAsyncResult ar)
    {
        // the pipe was closed (parent process died), so exit the child process too

        try
        {
            NamedPipeClientStream pipe = (NamedPipeClientStream)ar.AsyncState;
            pipe.EndRead(ar);
        }
        catch (IOException) { }

        Environment.Exit(1);
    }

    只是我的2018年版。
    将它用在Main()方法旁边。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
        using System.Management;
        using System.Diagnostics;

        ...

        // Called when the Main Window is closed
        protected override void OnClosed(EventArgs EventArgs)
        {
            string query ="Select * From Win32_Process Where ParentProcessId =" + Process.GetCurrentProcess().Id;
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
            ManagementObjectCollection processList = searcher.Get();
            foreach (var obj in processList)
            {
                object data = obj.Properties["processid"].Value;
                if (data != null)
                {
                    // retrieve the process
                    var childId = Convert.ToInt32(data);
                    var childProcess = Process.GetProcessById(childId);

                    // ensure the current process is still live
                    if (childProcess != null) childProcess.Kill();
                }
            }
            Environment.Exit(0);
        }


    使用事件处理程序在几个退出场景中进行挂钩:

    1
    2
    3
    4
    var process = Process.Start("program.exe");
    AppDomain.CurrentDomain.DomainUnload += (s, e) => { process.Kill(); process.WaitForExit(); };
    AppDomain.CurrentDomain.ProcessExit += (s, e) => { process.Kill(); process.WaitForExit(); };
    AppDomain.CurrentDomain.UnhandledException += (s, e) => { process.Kill(); process.WaitForExit(); };

    我创建了一个子进程管理库,其中由于双向WCF管道而监视父进程和子进程。如果子进程终止或父进程终止,则通知对方。
    还有一个调试器助手可以自动将VS调试器连接到已启动的子进程

    项目现场:

    http://www.crawler-lib.net/child-processes

    NuGet包:

    https://www.nuget.org/packages/ChildProcesses
    https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/


    我看到两个选择:

  • 如果你确切地知道可以启动什么子进程,并且你确定它们只是从你的主进程启动,那么你可以考虑只是通过名称搜索它们并杀死它们。
  • 迭代所有进程并杀死将进程作为父进程的每个进程(我猜你需要先杀死子进程)。这里解释了如何获取父进程ID。

  • 调用job.AddProcess在开始进程后做得更好:

    1
    2
    prc.Start();
    job.AddProcess(prc.Handle);

    在终止之前调用AddProcess时,子进程不会被终止。 (Windows 7 SP1)

    1
    2
    3
    4
    5
    6
    private void KillProcess(Process proc)
    {
        var job = new Job();
        job.AddProcess(proc.Handle);
        job.Close();
    }