2012-04-12 12 views
9

estoy usando el comando Runtime.getRuntime() .exec() en Java para iniciar un archivo por lotes que a su vez inicia otro proceso para la plataforma de Windows.¿Cómo termino un árbol de procesos desde Java?

javaw.exe(Process1) 
|___xyz.bat(Process2) 
     |___javaw.exe(Process3) 

Runtime.getRuntime(). Exec() devuelve un objeto de proceso que tiene un método de destruir, pero cuando se utiliza destroy(), que mata sólo el xyz.bat y deja colgando subproceso del archivo por lotes .

¿Hay una forma clara en Java de destruir el árbol de procesos comenzando con el proceso por lotes como raíz?

* No puedo usar cualquier bibliotecas personalizadas \ Elimina el archivo por lotes para pasar por alto el problema

+0

¿Puedo preguntar por qué no existe el requisito de biblioteca personalizada? En mi experiencia, tales requisitos generalmente tienen una razón muy pobre para existir, y pueden ser negociables con la explicación del motivo por el que se necesita una biblioteca (en este caso, una característica requerida que falta en la plataforma Java, es decir, una manera de enumerar subprocesos de un proceso principal). – Jules

Respuesta

10

Esto no es posible utilizar la API estándar de Java (ver edición al final del post para una actualización que cambia esto). Necesitará algún código nativo de alguna variedad. Usando JNA, he utilizado el código que se parece a esto:

public class Win32Process 
{ 
    WinNT.HANDLE handle; 
    int pid; 

    Win32Process (int pid) throws IOException 
    { 
     handle = Kernel32.INSTANCE.OpenProcess ( 
       0x0400| /* PROCESS_QUERY_INFORMATION */ 
       0x0800| /* PROCESS_SUSPEND_RESUME */ 
       0x0001| /* PROCESS_TERMINATE */ 
       0x00100000 /* SYNCHRONIZE */, 
       false, 
       pid); 
     if (handle == null) 
      throw new IOException ("OpenProcess failed: " + 
        Kernel32Util.formatMessageFromLastErrorCode (Kernel32.INSTANCE.GetLastError())); 
     this.pid = pid; 
    } 

    @Override 
    protected void finalize() throws Throwable 
    { 
     Kernel32.INSTANCE.CloseHandle (handle); 
    } 

    public void terminate() 
    { 
     Kernel32.INSTANCE.TerminateProcess (handle, 0); 
    } 

    public List<Win32Process> getChildren() throws IOException 
    { 
     ArrayList<Win32Process> result = new ArrayList<Win32Process>(); 
     WinNT.HANDLE hSnap = KernelExtra.INSTANCE.CreateToolhelp32Snapshot (KernelExtra.TH32CS_SNAPPROCESS, new DWORD(0)); 
     KernelExtra.PROCESSENTRY32.ByReference ent = new KernelExtra.PROCESSENTRY32.ByReference(); 
     if (!KernelExtra.INSTANCE.Process32First (hSnap, ent)) return result; 
     do { 
      if (ent.th32ParentProcessID.intValue() == pid) result.add (new Win32Process (ent.th32ProcessID.intValue())); 
     } while (KernelExtra.INSTANCE.Process32Next (hSnap, ent)); 
     Kernel32.INSTANCE.CloseHandle (hSnap); 
     return result; 
    } 

}

Este código utiliza las siguientes declaraciones del JNA que no están incluidos en la biblioteca estándar JNA:

public interface KernelExtra extends StdCallLibrary { 

    /** 
    * Includes all heaps of the process specified in th32ProcessID in the snapshot. To enumerate the heaps, see 
    * Heap32ListFirst. 
    */ 
    WinDef.DWORD TH32CS_SNAPHEAPLIST = new WinDef.DWORD(0x00000001); 

    /** 
    * Includes all processes in the system in the snapshot. To enumerate the processes, see Process32First. 
    */ 
    WinDef.DWORD TH32CS_SNAPPROCESS = new WinDef.DWORD(0x00000002); 

    /** 
    * Includes all threads in the system in the snapshot. To enumerate the threads, see Thread32First. 
    */ 
    WinDef.DWORD TH32CS_SNAPTHREAD = new WinDef.DWORD(0x00000004); 

    /** 
    * Includes all modules of the process specified in th32ProcessID in the snapshot. To enumerate the modules, see 
    * Module32First. If the function fails with ERROR_BAD_LENGTH, retry the function until it succeeds. 
    */ 
    WinDef.DWORD TH32CS_SNAPMODULE = new WinDef.DWORD(0x00000008); 

    /** 
    * Includes all 32-bit modules of the process specified in th32ProcessID in the snapshot when called from a 64-bit 
    * process. This flag can be combined with TH32CS_SNAPMODULE or TH32CS_SNAPALL. If the function fails with 
    * ERROR_BAD_LENGTH, retry the function until it succeeds. 
    */ 
    WinDef.DWORD TH32CS_SNAPMODULE32 = new WinDef.DWORD(0x00000010); 

    /** 
    * Includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID. 
    */ 
    WinDef.DWORD TH32CS_SNAPALL  = new WinDef.DWORD((TH32CS_SNAPHEAPLIST.intValue() | 
      TH32CS_SNAPPROCESS.intValue() | TH32CS_SNAPTHREAD.intValue() | TH32CS_SNAPMODULE.intValue())); 

    /** 
    * Indicates that the snapshot handle is to be inheritable. 
    */ 
    WinDef.DWORD TH32CS_INHERIT  = new WinDef.DWORD(0x80000000); 

    /** 
    * Describes an entry from a list of the processes residing in the system address space when a snapshot was taken. 
    */ 
    public static class PROCESSENTRY32 extends Structure { 

     public static class ByReference extends PROCESSENTRY32 implements Structure.ByReference { 
      public ByReference() { 
      } 

      public ByReference(Pointer memory) { 
       super(memory); 
      } 
     } 

     public PROCESSENTRY32() { 
      dwSize = new WinDef.DWORD(size()); 
     } 

     public PROCESSENTRY32(Pointer memory) { 
      useMemory(memory); 
      read(); 
     } 

     /** 
     * The size of the structure, in bytes. Before calling the Process32First function, set this member to 
     * sizeof(PROCESSENTRY32). If you do not initialize dwSize, Process32First fails. 
     */ 
     public WinDef.DWORD dwSize; 

     /** 
     * This member is no longer used and is always set to zero. 
     */ 
     public WinDef.DWORD cntUsage; 

     /** 
     * The process identifier. 
     */ 
     public WinDef.DWORD th32ProcessID; 

     /** 
     * This member is no longer used and is always set to zero. 
     */ 
     public BaseTSD.ULONG_PTR th32DefaultHeapID; 

     /** 
     * This member is no longer used and is always set to zero. 
     */ 
     public WinDef.DWORD th32ModuleID; 

     /** 
     * The number of execution threads started by the process. 
     */ 
     public WinDef.DWORD cntThreads; 

     /** 
     * The identifier of the process that created this process (its parent process). 
     */ 
     public WinDef.DWORD th32ParentProcessID; 

     /** 
     * The base priority of any threads created by this process. 
     */ 
     public WinDef.LONG pcPriClassBase; 

     /** 
     * This member is no longer used, and is always set to zero. 
     */ 
     public WinDef.DWORD dwFlags; 

     /** 
     * The name of the executable file for the process. To retrieve the full path to the executable file, call the 
     * Module32First function and check the szExePath member of the MODULEENTRY32 structure that is returned. 
     * However, if the calling process is a 32-bit process, you must call the QueryFullProcessImageName function to 
     * retrieve the full path of the executable file for a 64-bit process. 
     */ 
     public char[] szExeFile = new char[WinDef.MAX_PATH]; 
    } 


    // the following methods are in kernel32.dll, but not declared there in the current version of Kernel32: 

    /** 
    * Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes. 
    * 
    * @param dwFlags 
    * The portions of the system to be included in the snapshot. 
    * 
    * @param th32ProcessID 
    * The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate 
    * the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, 
    * TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are 
    * included in the snapshot. 
    * 
    * If the specified process is the Idle process or one of the CSRSS processes, this function fails and the last 
    * error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. 
    * 
    * If the specified process is a 64-bit process and the caller is a 32-bit process, this function fails and the 
    * last error code is ERROR_PARTIAL_COPY (299). 
    * 
    * @return 
    * If the function succeeds, it returns an open handle to the specified snapshot. 
    * 
    * If the function fails, it returns INVALID_HANDLE_VALUE. To get extended error information, call GetLastError. 
    * Possible error codes include ERROR_BAD_LENGTH. 
    */ 
    public WinNT.HANDLE CreateToolhelp32Snapshot(WinDef.DWORD dwFlags, WinDef.DWORD th32ProcessID); 

    /** 
    * Retrieves information about the first process encountered in a system snapshot. 
    * 
    * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function. 
    * @param lppe A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the 
    * executable file, the process identifier, and the process identifier of the parent process. 
    * @return 
    * Returns TRUE if the first entry of the process list has been copied to the buffer or FALSE otherwise. The 
    * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot 
    * does not contain process information. 
    */ 
    public boolean Process32First(WinNT.HANDLE hSnapshot, KernelExtra.PROCESSENTRY32.ByReference lppe); 

    /** 
    * Retrieves information about the next process recorded in a system snapshot. 
    * 
    * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function. 
    * @param lppe A pointer to a PROCESSENTRY32 structure. 
    * @return 
    * Returns TRUE if the next entry of the process list has been copied to the buffer or FALSE otherwise. The 
    * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot 
    * does not contain process information. 
    */ 
    public boolean Process32Next(WinNT.HANDLE hSnapshot, KernelExtra.PROCESSENTRY32.ByReference lppe); 


} 

Usted puede usar el método 'getChildren()' para obtener una lista de hijos, finalizar el padre y luego terminar recursivamente a los hijos.

Creo que se puede extra el PID de un java.lang.Process utilizando la reflexión (no he hecho esto, sin embargo, cambié a la creación de los procesos usando la API de Win32 para tener más control sobre ella)

Así que ponerlo todo junto, lo que se necesita algo como:

int pid = (some code to extract PID from the process you want to kill); 
Win32Process process = new Win32Process(pid); 
kill(process); 

public void kill(Win32Process target) throws IOException 
{ 
    List<Win32Process> children = target.getChildren(); 
    target.terminateProcess(); 
    for (Win32Process child : children) kill(child); 
} 

Editar

Resulta que esta deficiencia particular de la API de Java se fija en Java 9. Ver el trailer de la documentación de Java 9 here (si la página correcta no se carga, debe consultar la interfaz java.lang.ProcessHandle). Para el requisito de la pregunta anterior, el código sería ahora algo parecido a esto:

Process child = ...; 
kill (child.toHandle()); 

public void kill (ProcessHandle handle) 
{ 
    handle.descendants().forEach((child) -> kill(child)); 
    handle.destroy(); 
} 

(Tenga en cuenta que esto no se prueba - no he cambiado a Java 9 todavía, pero estoy leyendo activamente en ello)

0

No se puede matar a un árbol de procesos para las ventanas usando JDK. Necesita confiar en WinAPI. Tendrá que recurrir a los comandos nativos o las bibliotecas JNI, todas las cuales dependen de la plataforma y son más complejas que una solución pura de Java.

Un enlace muestra JNI Example

+0

Desafortunadamente no puedo usar ninguna biblioteca externa o personalizada. Sin embargo, puedo cambiar el archivo de proceso por lotes. ¿Hay alguna forma de detectar una señal de término en el lote enviado por Process.destroy() desde Java? y luego usarlo para matar el subproceso? – srami

+0

Puede gestionar procesos utilizando el archivo por lotes. Por favor, vaya a través del enlace para obtener varias opciones disponibles para administrar. http://www.robvanderwoude.com/processes.php – Phani

+0

No creo que haya ninguna forma de hacer que un archivo de proceso por lotes de Windows capte tal señal: Java usa nativamente TerminateProcess, que mata el proceso directamente sin enviar un señal a ella primero. Además, si actualmente se está ejecutando un proceso hijo, el archivo por lotes debería esperar a que termine para poder hacer cualquier otra cosa, y no hay forma de que Java rescinda el proceso secundario. Deberá usar bibliotecas externas si desea hacer esto. – Jules

1

Una solución alternativa, si controla el proceso secundario así como también el archivo por lotes, sería hacer que el proceso secundario cree un hilo, abra un ServerSocket, escuche la conexión y llame a System.exit() si recibe una contraseña correcta en él.

Puede haber complicaciones si necesita varias instancias simultáneas; en ese punto necesitaría alguna forma de asignarles números de puerto.

0

Aquí hay otra opción. Use este script de powershell para ejecutar su script bat. Cuando desee eliminar el árbol, finalice el proceso del script de powershell y ejecutará taskkill en su subproceso automáticamente. Lo llamo taskkill dos veces porque, en algunos casos, no se lleva a cabo el primer intento.

Param(
    [string]$path 
) 

$p = [Diagnostics.Process]::Start("$path").Id 

try { 
    while($true) { 
     sleep 100000 
    } 
} finally { 
    taskkill /pid $p 
    taskkill /pid $p 
} 
0

Con java 9, matar el proceso principal acaba con todo el árbol de procesos. Se podría hacer algo como esto:

Process ptree = Runtime.getRuntime().exec("cmd.exe","/c","xyz.bat"); 
// wait logic 
ptree.destroy(); 

favor, eche un vistazo a este blog y ver el reparto con árboles de procesos ejemplo.

Cuestiones relacionadas