c# - visual - Matar proceso hijo cuando se mata el proceso principal
system.diagnostics.process c# example (11)
Aquí hay una alternativa que puede funcionar para algunos cuando tienes control del código que ejecuta el proceso hijo. El beneficio de este enfoque es que no requiere ninguna llamada nativa de Windows.
La idea básica es redirigir la entrada estándar del niño a un flujo cuyo otro extremo está conectado al padre, y usar ese flujo para detectar cuando el padre se ha ido. Cuando utiliza System.Diagnostics.Process
para iniciar el elemento secundario, es fácil asegurarse de que su entrada estándar sea redirigida:
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
Y luego, en el proceso hijo, aproveche el hecho de que Read
s del flujo de entrada estándar siempre regresará con al menos 1 byte hasta que se cierre el flujo, cuando comenzarán a devolver 0 bytes. Un resumen de la forma en que terminé haciendo esto es a continuación; my way también usa una bomba de mensajes para mantener el hilo principal disponible para otras cosas además de mirar el estándar, pero este enfoque general podría usarse sin bombas de mensajes.
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();
}
}
Advertencias a este enfoque:
el .exe hijo real que se inicia debe ser una aplicación de consola, por lo que permanece adjunto a stdin / out / err. Como en el ejemplo anterior, adapté fácilmente mi aplicación existente que usaba una bomba de mensajes (pero no mostraba una GUI) simplemente creando un pequeño proyecto de consola que hacía referencia al proyecto existente, instanciando el contexto de mi aplicación y llamando a
Application.Run()
dentro del métodoMain
de la consola .exe.Técnicamente, esto simplemente señala el proceso secundario cuando el padre sale, por lo que funcionará tanto si el proceso padre salió normalmente como si se bloqueó, pero todavía depende de que el niño realice su propio cierre. Esto puede o no ser lo que quieras ...
Estoy creando nuevos procesos usando System.Diagnostics.Process
class desde mi aplicación.
Quiero que estos procesos se eliminen cuando / si mi aplicación se ha bloqueado. Pero si elimino mi aplicación del Administrador de tareas, los procesos secundarios no se eliminan.
¿Hay alguna forma de hacer que los procesos secundarios dependan del proceso principal?
Creé una biblioteca de gestión de procesos secundarios donde se supervisan el proceso principal y el proceso secundario debido a un conducto WCF bidireccional. Si el proceso secundario finaliza o el proceso principal finaliza, se notifica a los demás. También hay un auxiliar de depurador disponible que conecta automáticamente el depurador VS al proceso hijo iniciado
Sitio del proyecto:
http://www.crawler-lib.net/child-processes
Paquetes NuGet:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
De este foro , acredite a ''Josh''.
Application.Quit()
y Process.Kill()
son soluciones posibles, pero han demostrado no ser confiables. Cuando su aplicación principal muere, todavía le quedan procesos secundarios en ejecución. Lo que realmente queremos es que los procesos secundarios mueran tan pronto como muere el proceso principal.
La solución es usar "objetos de trabajo" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
La idea es crear un "objeto de trabajo" para su aplicación principal y registrar sus procesos secundarios con el objeto de trabajo. Si el proceso principal muere, el sistema operativo se encargará de terminar los procesos secundarios.
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);
}
}
Mirando al constructor ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
La clave aquí es configurar el objeto de trabajo correctamente. En el constructor, estoy estableciendo los "límites" en 0x2000, que es el valor numérico de JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
MSDN define esta bandera como:
Hace que todos los procesos asociados con el trabajo finalicen cuando se cierra el último identificador del trabajo.
Una vez que esta clase está configurada ... solo tiene que registrar cada proceso secundario con el trabajo. Por ejemplo:
[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);
Esta publicación pretende ser una extensión de la respuesta de @Matt Howells, específicamente para aquellos que tienen problemas con el uso de Objetos de trabajo en Vista o Win7 , especialmente si obtienes un error de acceso denegado (''5'') cuando llamas a AssignProcessToJobObject.
tl; dr
Para garantizar la compatibilidad con Vista y Win7, agregue el siguiente manifiesto al proceso principal de .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://.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>
Tenga en cuenta que cuando agrega un manifiesto nuevo en Visual Studio 2012, ya contiene el fragmento anterior, por lo que no necesita copiarlo de la escucha. También incluirá un nodo para Windows 8.
explicación completa
Su asociación de trabajo fallará con un error de acceso denegado si el proceso que está iniciando ya está asociado a otro trabajo. Ingrese al Asistente de compatibilidad de programas, que, comenzando en Windows Vista, asignará todo tipo de procesos a sus propios trabajos.
En Vista, puede marcar que su aplicación se excluirá de PCA simplemente incluyendo un manifiesto de la aplicación. Visual Studio parece hacer esto para aplicaciones .NET automáticamente, así que estás bien allí.
Un manifiesto simple ya no lo corta en Win7. [1] Allí, debes especificar específicamente que eres compatible con Win7 con la etiqueta en tu manifiesto. [2]
Esto me llevó a preocuparme por Windows 8. ¿Tendré que cambiar mi manifiesto una vez más? Aparentemente hay una ruptura en las nubes, ya que Windows 8 ahora permite que un proceso pertenezca a múltiples trabajos. [3] Así que aún no lo he probado, pero me imagino que esta locura ya terminará si simplemente incluye un manifiesto con la información supportedOS.
Consejo 1 : Si está desarrollando una aplicación .NET con Visual Studio, como yo, aquí [4] encontrará algunas instrucciones útiles sobre cómo personalizar el manifiesto de su aplicación.
Consejo 2 : tenga cuidado al iniciar su aplicación desde Visual Studio. Descubrí que, después de agregar el manifiesto apropiado, todavía tenía problemas con PCA al iniciar desde Visual Studio, incluso si usaba Start without Debugging. Sin embargo, el lanzamiento de mi aplicación desde Explorer funcionó. Después de agregar manualmente devenv para su exclusión de PCA utilizando el registro, también comenzaron a funcionar las aplicaciones de inicio que usaban Objetos de Trabajo de VS. [5]
Consejo 3 : Si alguna vez quiere saber si PCA es su problema, intente iniciar su aplicación desde la línea de comandos o copie el programa en una unidad de red y ejecútelo desde allí. PCA se deshabilita automáticamente en esos contextos.
[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 : "Un proceso puede asociarse con más de un trabajo en Windows 8"
[4] ¿Cómo puedo integrar un manifiesto de aplicación en una aplicación utilizando VS2008?
[5] ¿Cómo detener el depurador de Visual Studio iniciando mi proceso en un objeto de trabajo?
Esta respuesta comenzó con la excelente respuesta de @Matt Howells más otras (ver enlaces en el código a continuación). Mejoras:
- Admite 32 bits y 64 bits.
- Soluciona algunos problemas en la respuesta de @Matt Howells:
- La pequeña pérdida de memoria de
extendedInfoPtr
- El error de compilación ''Win32'', y
- Una excepción de desequilibrio de pila que recibí en la llamada a
CreateJobObject
(usando Windows 10, Visual Studio 2015, 32 bits).
- La pequeña pérdida de memoria de
- Nombra el trabajo, por lo que si usa SysInternals, por ejemplo, puede encontrarlo fácilmente.
- Tiene una API algo más simple y menos código.
A continuación, le mostramos cómo usar este código:
// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);
Para admitir Windows 7 requiere:
- Una aplicación simple. Un cambio manifiesto como describe @adam smith .
- Configuración del registro que se agregará si está usando Visual Studio.
En mi caso, no necesitaba soportar Windows 7, así que tengo una simple verificación en la parte superior del constructor estático a continuación.
/// <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://.com/a/4657392/386091
/// https://.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)
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://.com/a/4232259/386091
// https://.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;
}
Probé cuidadosamente las versiones de 32 bits y de 64 bits de las estructuras mediante la comparación programática de las versiones administradas y nativas entre sí (el tamaño total y los desplazamientos para cada miembro).
He probado este código en Windows 7, 8 y 10.
Estaba buscando una solución a este problema que no requiere código no administrado. Tampoco pude usar la redirección de entrada / salida estándar porque era una aplicación de Windows Forms.
Mi solución fue crear una tubería con nombre en el proceso principal y luego conectar el proceso secundario a la misma tubería. Si el proceso principal se cierra, el tubo se rompe y el niño puede detectarlo.
A continuación se muestra un ejemplo con dos aplicaciones de consola:
Padre
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();
}
}
Niño
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);
}
Existe otro método relevante, fácil y efectivo, para finalizar los procesos secundarios al finalizar el programa. Puede implementar y adjuntar un depurador desde el padre; cuando finaliza el proceso principal, el sistema operativo eliminará los procesos hijos. Puede ir en ambos sentidos al conectar un depurador al padre del niño (tenga en cuenta que solo puede adjuntar un depurador a la vez). Puede encontrar más información sobre el tema here .
Aquí tiene una clase de utilidad que inicia un nuevo proceso y le agrega un depurador. Ha sido adaptado de este post por Roger Knapp. El único requisito es que ambos procesos necesiten compartir el mismo bitness. No puede depurar un proceso de 32 bits de un proceso de 64 bits o viceversa.
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");
Una forma es pasar el PID del proceso principal al niño. El niño periódicamente sondeará si el proceso con el pid especificado existe o no. Si no, simplemente lo abandonará.
También puede usar el método Process.WaitForExit en el método secundario para recibir una notificación cuando finalice el proceso principal, pero puede no funcionar en el caso del Administrador de tareas.
Utilice controladores de eventos para crear enlaces en algunos escenarios de salida:
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(); };
Veo dos opciones:
- Si sabe exactamente qué proceso hijo podría iniciarse y está seguro de que solo se iniciaron a partir de su proceso principal, entonces podría considerar simplemente buscarlos por su nombre y eliminarlos.
- Itere a través de todos los procesos y mate cada proceso que tenga su proceso como padre (supongo que primero debe matar los procesos hijos). Here se explica cómo puede obtener la identificación del proceso principal.
call job.AddProcess es mejor que hacer después del inicio del proceso:
prc.Start();
job.AddProcess(prc.Handle);
Al llamar a AddProcess antes de terminar, los procesos secundarios no se eliminan. (Windows 7 SP1)
private void KillProcess(Process proc)
{
var job = new Job();
job.AddProcess(proc.Handle);
job.Close();
}