在父进程被终止时杀死子进程

Sib*_*Guy 147 .net c# process

我正在使用System.Diagnostics.Process我的应用程序中的类创建新进程.

当/如果我的应用程序崩溃了,我希望这个进程被杀死.但是如果我从任务管理器中删除我的应用程序,子进程就不会被杀死.

有没有办法让子进程依赖于父进程?

Mat*_*lls 165

这个论坛,归功于'Josh'.

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

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

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

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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

JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
Run Code Online (Sandbox Code Playgroud)

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

MSDN将此标志定义为:

与作业关联的所有进程在作业的最后一个句柄关闭时终止.

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

[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);
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,我无法在64位模式下运行它.[这里](http://stackoverflow.com/a/9164742/607959)我发布了一个基于此的工作示例. (6认同)
  • 对于64位模式应用程序 - > http://stackoverflow.com/a/5976162对于Vista/Win7问题 - > http://social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/71c9599e-a3d5 -4b79-bfc1-1800565c5b8a / (6认同)
  • 我会添加一个链接到[CloseHandle](http://msdn.microsoft.com/en-us/library/ms724211(v = vs.85).aspx) (4认同)
  • 好的,[MSDN](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684147(v = vs.85).aspx)说:JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE标志需要使用JOBOBJECT_EXTENDED_LIMIT_INFORMATION结构. (3认同)
  • @Matt Howells - "Win32.CloseHandle"在哪里发源?这是从kernel32.dll导入的吗?那里有一个匹配的签名,但你没有像其他API函数那样明确地导入它. (2认同)
  • 我修复了编译错误和内存泄漏。我还用对`PInvoke.Win32` 的简单包引用替换了所有 p/invoke 签名和本机结构。你可以在这里看到最终结果:https://gist.github.com/AArnott/2609636d2f2369495abe76e8a01446a4 (2认同)

ada*_*ith 47

这篇文章旨在作为@Matt Howells答案的扩展,特别是对于那些在Vista或Win7下使用Job Objects时遇到问题的人,特别是如果在调用AssignProcessToJobObject时遇到访问被拒绝错误('5').

TL;博士

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

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <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 -->

    <application>
      <!--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>
Run Code Online (Sandbox Code Playgroud)

请注意,在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调试器在作业对象中启动我的进程?

  • 这些都是该主题的重要补充,谢谢!我利用了该答案的各个方面,包括链接。 (2认同)

Ron*_*Ron 45

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

  • 支持32位和64位.
  • 解决了@Matt Howells的回答中的一些问题:
    1. 小内存泄漏了 extendedInfoPtr
    2. 'Win32'编译错误,和
    3. 我在调用中遇到的堆栈不平衡异常CreateJobObject(使用Windows 10,Visual Studio 2015,32位).
  • 命名作业,例如,如果您使用SysInternals,则可以轻松找到它.
  • 有一个更简单的API和更少的代码.

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

// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);
Run Code Online (Sandbox Code Playgroud)

要支持Windows 7,需要:

在我的情况下,我不需要支持Windows 7,所以我在下面的静态构造函数的顶部进行了简单的检查.

/// <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;
}
Run Code Online (Sandbox Code Playgroud)

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

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


mba*_*ton 16

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

注意这种方法:

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

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

  • 我曾经多次使用过这个技巧.效果很好 (3认同)

Gio*_*rgi 10

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

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

  • 您可以挂接到流程类上的 Exit 事件,而不是轮询。 (2认同)

Als*_*sty 9

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

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

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

家长

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

孩子

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);
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ira 8

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

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

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?
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

    new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");
Run Code Online (Sandbox Code Playgroud)


Jus*_*ris 6

使用事件处理程序在一些退出场景中创建钩子:

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(); };
Run Code Online (Sandbox Code Playgroud)

  • 当父进程从任务管理器终止时,这些事件似乎不会被调用 (5认同)
  • 如此简单却又如此有效。 (2认同)

归档时间:

查看次数:

68682 次

最近记录:

6 年,10 月 前