Matar processo filho quando o processo pai é morto

156

Estou criando novos processos usando a System.Diagnostics.Processclasse do meu aplicativo.

Quero que esses processos sejam eliminados quando / se meu aplicativo falhar. Mas se eu matar meu aplicativo do Gerenciador de Tarefas, os processos filhos não serão mortos.

Existe alguma maneira de tornar os processos filhos dependentes do processo pai?

SiberianGuy
fonte

Respostas:

176

Deste fórum , o crédito ao 'Josh'.

Application.Quit()e Process.Kill()são possíveis soluções, mas provaram não ser confiáveis. Quando seu aplicativo principal morre, você ainda fica com os processos filhos em execução. O que realmente queremos é que os processos filhos morram assim que o processo principal morre.

A solução é usar "objetos de trabalho" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .

A idéia é criar um "objeto de trabalho" para o aplicativo principal e registrar os processos filhos no objeto de trabalho. Se o processo principal morrer, o sistema operacional cuidará de encerrar os processos filhos.

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

}

Olhando para o construtor ...

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

A chave aqui é configurar o objeto de trabalho corretamente. No construtor, estou configurando os "limites" para 0x2000, que é o valor numérico paraJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE .

O MSDN define esse sinalizador como:

Faz com que todos os processos associados ao trabalho sejam finalizados quando o último identificador do trabalho é fechado.

Depois que essa classe é configurada ... você apenas precisa registrar cada processo filho no trabalho. Por exemplo:

[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
fonte
4
Gostaria de adicionar um link para CloseHandle
Austin Salonen 16/03
6
Infelizmente, não consegui executar isso no modo de 64 bits. Aqui eu postei um exemplo de trabalho, que é baseado nisso.
Alexander Yezutov 6/02/2012
2
@ Matt Howells - De onde se Win32.CloseHandleoriginam? Isso é importado do kernel32.dll? Há uma assinatura correspondente lá, mas você não a está importando explicitamente como as outras funções da API.
Nome de tela esotérico
6
Para aplicativos no modo de 64 bits -> stackoverflow.com/a/5976162 Para o Vista / Win7 -> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/…
hB0
3
Ok, o MSDN diz: O sinalizador JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE requer o uso de uma estrutura JOBOBJECT_EXTENDED_LIMIT_INFORMATION.
Serg
54

Esta resposta começou com a excelente resposta de @Matt Howells e outras (consulte os links no código abaixo). Melhorias:

  • Suporta 32 bits e 64 bits.
  • Corrige alguns problemas na resposta do @Matt Howells:
    1. O pequeno vazamento de memória de extendedInfoPtr
    2. O erro de compilação 'Win32' e
    3. Uma exceção desequilibrada da pilha que recebi na chamada CreateJobObject(usando o Windows 10, Visual Studio 2015, 32 bits).
  • Nomeie o trabalho, portanto, se você usar o SysInternals, por exemplo, poderá encontrá-lo facilmente.
  • Possui uma API um pouco mais simples e menos código.

Veja como usar esse código:

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

Para oferecer suporte ao Windows 7, é necessário:

No meu caso, não precisava oferecer suporte ao Windows 7, portanto, tenho uma verificação simples na parte superior do construtor estático abaixo.

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

Testei cuidadosamente as versões de 32 e 64 bits das estruturas comparando programaticamente as versões gerenciada e nativa entre si (o tamanho geral e as compensações de cada membro).

Testei esse código no Windows 7, 8 e 10.

Ron
fonte
que tal fechar a alça do trabalho?
Frank Q.
@FrankQ. É importante deixar o Windows fechar o s_jobHandle para nós quando o processo terminar, porque o processo pode ser encerrado inesperadamente (como travar ou se o usuário usar o Gerenciador de Tarefas). Veja meu comentário no s_jobHandle's.
Ron
Está funcionando para mim. Posso perguntar qual a sua opinião sobre o uso da sinalização JOB_OBJECT_LIMIT_BREAKAWAY_OK? docs.microsoft.com/en-us/windows/desktop/api/winnt/…
Yiping
1
@yiping Parece que CREATE_BREAKAWAY_FROM_JOB permite que seu processo filho inicie um processo que pode sobreviver ao processo original. Esse é um requisito diferente do que o OP solicitou. Em vez disso, se você puder fazer com que seu processo original despeje esse processo de longa duração (e não use o ChildProcessTracker), isso seria mais simples.
Ron
@ Ron Você pode adicionar uma sobrecarga, que aceite apenas o identificador do processo e não todo o processo?
Jannik
47

Esta publicação pretende ser uma extensão da resposta do @Matt Howells, especificamente para aqueles que têm problemas com o uso de objetos de trabalho no Vista ou no Win7 , especialmente se você receber um erro de acesso negado ('5') ao chamar AssignProcessToJobObject.

tl; dr

Para garantir a compatibilidade com o Vista e o Win7, adicione o seguinte manifesto ao processo pai do .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>

Observe que, quando você adiciona um novo manifesto no Visual Studio 2012, ele já contém o snippet acima, para que você não precise copiá-lo do hear. Também incluirá um nó para o Windows 8.

explicação completa

Sua associação ao trabalho falhará com um erro de acesso negado se o processo que você está iniciando já estiver associado a outro trabalho. Digite o Assistente de Compatibilidade de Programa, que, a partir do Windows Vista, atribuirá todos os tipos de processos a seus próprios trabalhos.

No Vista, você pode marcar seu aplicativo para ser excluído do PCA simplesmente incluindo um manifesto de aplicativo. O Visual Studio parece fazer isso para aplicativos .NET automaticamente, então você está bem.

Um manifesto simples não o corta mais no Win7. [1] Lá, é necessário especificar especificamente que você é compatível com o Win7 com a tag no seu manifesto. [2]

Isso me levou a me preocupar com o Windows 8. Terei que mudar meu manifesto mais uma vez? Aparentemente, há uma quebra nas nuvens, pois o Windows 8 agora permite que um processo pertença a vários trabalhos. [3] Então, ainda não testei, mas imagino que essa loucura acabará agora se você simplesmente incluir um manifesto com as informações suportadas do OS.

Dica 1 : se você está desenvolvendo um aplicativo .NET com o Visual Studio, como eu estava, aqui estão algumas instruções legais sobre como personalizar o manifesto do aplicativo.

Dica 2 : tenha cuidado ao iniciar seu aplicativo no Visual Studio. Descobri que, depois de adicionar o manifesto apropriado, ainda havia problemas com o PCA ao iniciar a partir do Visual Studio, mesmo se eu usasse Iniciar sem Depuração. Iniciar meu aplicativo no Explorer funcionou, no entanto. Após adicionar manualmente o devenv para exclusão do PCA usando o registro, o início de aplicativos que usavam Objetos de Trabalho do VS também começou a funcionar. [5]

Dica 3 : Se você quiser saber se o PCA é seu problema, tente iniciar o aplicativo a partir da linha de comandos ou copie o programa em uma unidade de rede e execute-o a partir daí. O PCA é automaticamente desativado nesses contextos.

[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an -installer-take-2-porque-mudamos-as-regras-em-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 : "Um processo pode ser associado a mais de um trabalho no Windows 8"

[4] Como posso incorporar um manifesto de aplicativo em um aplicativo usando o VS2008?

[5] Como parar o depurador do Visual Studio que inicia o meu processo em um objeto de trabalho?

Adam Smith
fonte
2
Essas são ótimas adições ao tópico, obrigado! Eu fiz uso de todos os aspectos desta resposta, incluindo os links.
Johnny Kauffman
16

Aqui está uma alternativa que pode funcionar para alguns quando você tem controle do código que o processo filho executa. O benefício dessa abordagem é que ela não requer nenhuma chamada nativa do Windows.

A idéia básica é redirecionar a entrada padrão da criança para um fluxo cuja outra extremidade está conectada ao pai e usar esse fluxo para detectar quando o pai foi embora. Quando você System.Diagnostics.Processinicia o filho, é fácil garantir que sua entrada padrão seja redirecionada:

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

E, no processo filho, aproveite o fato de que Reads do fluxo de entrada padrão sempre retornam com pelo menos 1 byte até que o fluxo seja fechado, quando começarão a retornar 0 bytes. Um resumo da maneira como acabei fazendo isso está abaixo; meu modo também usa uma bomba de mensagens para manter o encadeamento principal disponível para outras coisas além da observação padrão, mas essa abordagem geral também pode ser usada sem bombas de mensagens.

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

Advertências para esta abordagem:

  1. o .exe filho real que é iniciado deve ser um aplicativo de console para permanecer anexado a stdin / out / err. Como no exemplo acima, adaptei facilmente meu aplicativo existente que usava uma bomba de mensagens (mas não mostrava uma GUI) criando apenas um pequeno projeto de console que referenciava o projeto existente, instanciando o contexto do meu aplicativo e chamando Application.Run()dentro do Mainmétodo console .exe.

  2. Tecnicamente, isso apenas sinaliza o processo filho quando o pai sai, portanto, ele funcionará se o processo pai saiu normalmente ou travou, mas ainda depende do processo filho executar seu próprio desligamento. Isso pode ou não ser o que você deseja ...

mbaynton
fonte
11

Uma maneira é passar o PID do processo pai para o filho. A criança pesquisará periodicamente se o processo com o pid especificado existe ou não. Caso contrário, apenas sairá.

Você também pode usar o método Process.WaitForExit no método filho para ser notificado quando o processo pai terminar, mas pode não funcionar no caso do Gerenciador de Tarefas.

Giorgi
fonte
Como posso passar o PID pai para filho? Existe alguma solução do sistema? Não consigo modificar os binários do processo filho.
SiberianGuy
1
Bem, se você não pode modificar o processo filho, não poderá usar minha solução, mesmo que passe o PID para ele.
Giorgi
@Idsa Você pode transmiti-lo através da linha de comando:Process.Start(string fileName, string arguments)
Distortum 17/03/2012
2
Em vez de pesquisar, você pode conectar-se ao evento Exit na classe de processo.
RichardOD
Tentei, mas o processo pai nem sempre sai se tiver filhos vivos (pelo menos no meu caso Cobol-> NET). Fácil de verificar observando a hierarquia do processo no Sysinternals ProcessExplorer.
Ivan Ferrer Villa
8

Existe outro método relevante, fácil e eficaz, para concluir os processos filhos na finalização do programa. Você pode implementar e anexar um depurador a eles a partir do pai; quando o processo pai terminar, os processos filhos serão eliminados pelo sistema operacional. Ele pode seguir os dois sentidos ao anexar um depurador ao pai do filho (observe que você pode anexar apenas um depurador por vez). Você pode encontrar mais informações sobre o assunto aqui .

Aqui você tem uma classe de utilitário que inicia um novo processo e anexa um depurador a ele. Foi adaptado deste post por Roger Knapp. O único requisito é que ambos os processos precisem compartilhar a mesma testemunha. Você não pode depurar um processo de 32 bits de um processo de 64 bits ou vice-versa.

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?
        }
    }
}

Uso:

    new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");
Marco Regueira
fonte
8

Eu estava procurando uma solução para esse problema que não exigisse código não gerenciado. Também não pude usar o redirecionamento de entrada / saída padrão porque era um aplicativo Windows Forms.

Minha solução foi criar um pipe nomeado no processo pai e conectar o processo filho ao mesmo pipe. Se o processo pai sair, o tubo será quebrado e o filho poderá detectá-lo.

Abaixo está um exemplo usando dois aplicativos de console:

Pai

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

Criança

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);
}
Alsty
fonte
3

Use manipuladores de eventos para fazer ganchos em alguns cenários de saída:

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(); };
Justin Harris
fonte
Tão simples, mas tão eficaz.
uncommon_name
2

Apenas minha versão de 2018. Use-o de lado o seu método Main ().

    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);
    }
Lenor
fonte
4
Duvido que isso seja executado quando o processo pai estiver sendo morto .
springy76
1

Eu vejo duas opções:

  1. Se você souber exatamente qual processo filho poderia ser iniciado e tiver certeza de que eles foram iniciados apenas no processo principal, considere simplesmente procurá-los pelo nome e matá-los.
  2. Repita todos os processos e elimine todos os processos que tenham seu processo como pai (acho que você precisa matar os processos filhos primeiro). Aqui está explicado como você pode obter o ID do processo pai.
Stefan Egli
fonte
1

Criei uma biblioteca de gerenciamento de processos filho em que o processo pai e o processo filho são monitorados devido a um canal WCF bidirecional. Se o processo filho terminar ou o processo pai terminar, será notificado. Há também um auxiliar de depurador disponível, que anexa automaticamente o depurador do VS ao processo filho iniciado

Projetar site:

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

Pacotes NuGet:

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

Thomas Maierhofer
fonte
0

chame job.AddProcess para fazer melhor após o início do processo:

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

Ao chamar AddProcess antes da finalização, os processos filhos não são eliminados. (Windows 7 SP1)

private void KillProcess(Process proc)
{
    var job = new Job();
    job.AddProcess(proc.Handle);
    job.Close();
}
Alexey
fonte
Chamar job.AddProcess após o início do processo eliminaria o objetivo de usar esse objeto.
Serg
0

Mais uma adição à abundante riqueza de soluções propostas até agora ...

O problema com muitos deles é que eles dependem do processo pai e filho para desligar de maneira ordenada, o que nem sempre é verdade quando o desenvolvimento está em andamento. Descobri que meu processo filho costumava ficar órfão sempre que eu encerrava o processo pai no depurador, o que exigia que eu matasse o (s) processo (s) órfão (s) com o Gerenciador de Tarefas para reconstruir minha solução.

A solução: passe o ID do processo pai na linha de comando (ou até menos invasiva, nas variáveis ​​de ambiente) do processo filho.

No processo pai, o ID do processo está disponível como:

  Process.CurrentProcess.Id;

No processo filho:

Process parentProcess = Process.GetProcessById(parentProcessId);
parentProcess.Exited += (s, e) =>
{
    // clean up what you can.
    this.Dispose();
    // maybe log an error
    ....

    // And terminate with prejudice! 
    //(since something has already gone terribly wrong)
    Process.GetCurrentProcess().Kill();
}

Penso se isso é uma prática aceitável no código de produção. Por um lado, isso nunca deve acontecer. Mas, por outro lado, pode significar a diferença entre reiniciar um processo e reiniciar um servidor de produção. E o que nunca deveria acontecer frequentemente acontece.

E com certeza é útil ao depurar problemas de desligamento ordenado.

Robin Davies
fonte