代码之家  ›  专栏  ›  技术社区  ›  Beto Neto

Windows中的JNA:使用Windows作业自动终止子进程

  •  2
  • Beto Neto  · 技术社区  · 6 年前

    我需要在Windows中的Java应用程序中启动子进程,最终我的Java应用程序可以通过任务管理器被杀死/终止。因此,我需要将这个子进程与父进程“链接”,以便在父进程终止时两者都被终止。

    在Windows API中,我们有 CreateJobObject 以及:

    基于硒类 Kernel32 WindowsProcessGroup 创建了这个类:

    import java.util.Arrays;
    import java.util.List;
    import java.util.Scanner;
    import java.util.stream.Collectors;
    
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    import com.sun.jna.platform.win32.WinBase;
    import com.sun.jna.platform.win32.WinDef;
    import com.sun.jna.platform.win32.WinNT;
    import com.sun.jna.platform.win32.WinNT.HANDLE;
    import com.sun.jna.ptr.IntByReference;
    import com.sun.jna.win32.W32APIOptions;
    
    public final class ProcessChildAttached {
    
        public static abstract class Structure extends com.sun.jna.Structure {
    
            public Structure() {
                super();
            }
    
            public Structure(Pointer p) {
                super(p);
            }
    
            private List<String> fields;
    
            protected Class<? extends Structure> getFieldsClass() {
                Class<? extends Structure> ret = this.getClass();
                if (ByReference.class.isAssignableFrom(ret) && com.sun.jna.Structure.class.isAssignableFrom(ret.getSuperclass())) {
                    ret = (Class<? extends Structure>) ret.getSuperclass();
                }
                return ret;
            }
    
            @Override
            protected List<String> getFieldOrder() {
                if (fields == null) {
                    fields = Arrays.stream(getFieldsClass().getDeclaredFields()).map(df -> df.getName()).collect(Collectors.toList());
                }
                return fields;
            }
    
        }
    
        static interface Kernel32 extends com.sun.jna.platform.win32.Kernel32 {
    
            Kernel32 INSTANCE = Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
    
            WinNT.HANDLE CreateJobObject(WinBase.SECURITY_ATTRIBUTES attrs, String name);
    
            boolean SetInformationJobObject(HANDLE hJob, int JobObjectInfoClass, Pointer lpJobObjectInfo, int cbJobObjectInfoLength);
    
            boolean AssignProcessToJobObject(HANDLE hJob, HANDLE hProcess);
    
            boolean TerminateJobObject(HANDLE hJob, long uExitCode);
    
            int ResumeThread(HANDLE hThread);
    
            // 0x00000800
            int JOB_OBJECT_LIMIT_BREAKAWAY_OK = 2048;
    
            // 0x00002000
            int JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 8192;
    
            // see SetInformationJobObject at msdn
            int JobObjectExtendedLimitInformation = 9;
    
            // see SetInformationJobObject at msdn
            int JobObjectBasicUIRestrictions = 4;
    
            // 0x00000020
            int JOB_OBJECT_UILIMIT_GLOBALATOMS = 0x00000020;
    
            // 0x00000004
            int CREATE_SUSPENDED = 4;
    
            // 0x01000000
            int CREATE_BREAKAWAY_FROM_JOB = 16777216;
    
            static class JOBJECT_BASIC_LIMIT_INFORMATION extends Structure {
                public LARGE_INTEGER PerProcessUserTimeLimit;
                public LARGE_INTEGER PerJobUserTimeLimit;
                public int LimitFlags;
                public SIZE_T MinimumWorkingSetSize;
                public SIZE_T MaximumWorkingSetSize;
                public int ActiveProcessLimit;
                public ULONG_PTR Affinity;
                public int PriorityClass;
                public int SchedulingClass;
            }
    
            static class IO_COUNTERS extends Structure {
    
                public ULONGLONG ReadOperationCount;
                public ULONGLONG WriteOperationCount;
                public ULONGLONG OtherOperationCount;
                public ULONGLONG ReadTransferCount;
                public ULONGLONG WriteTransferCount;
                public ULONGLONG OtherTransferCount;
    
            }
    
            static class JOBJECT_EXTENDED_LIMIT_INFORMATION extends Structure {
    
                public JOBJECT_EXTENDED_LIMIT_INFORMATION() {
                }
    
                public JOBJECT_EXTENDED_LIMIT_INFORMATION(Pointer memory) {
                    super(memory);
                }
    
                public JOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
                public IO_COUNTERS IoInfo;
                public SIZE_T ProcessMemoryLimit;
                public SIZE_T JobMemoryLimit;
                public SIZE_T PeakProcessMemoryUsed;
                public SIZE_T PeakJobMemoryUsed;
    
                public static class ByReference extends JOBJECT_EXTENDED_LIMIT_INFORMATION implements Structure.ByReference {
    
                    public ByReference() {
                    }
    
                    public ByReference(Pointer memory) {
                        super(memory);
                    }
                }
            }
    
            static class JOBOBJECT_BASIC_UI_RESTRICTIONS extends Structure {
                public JOBOBJECT_BASIC_UI_RESTRICTIONS() {
                }
    
                public JOBOBJECT_BASIC_UI_RESTRICTIONS(Pointer memory) {
                    super(memory);
                }
    
                public int UIRestrictionsClass;
    
                public static class ByReference extends JOBOBJECT_BASIC_UI_RESTRICTIONS implements Structure.ByReference {
                    public ByReference() {
                    }
    
                    public ByReference(Pointer memory) {
                        super(memory);
                    }
                }
            }
    
        }
    
        private Kernel32 kernel32 = Kernel32.INSTANCE;
    
        private String cmd;
        private String workingDirectory;
        private HANDLE hJob;
        private WinBase.PROCESS_INFORMATION.ByReference pi;
    
        public ProcessChildAttached(String cmd, String workingDirectory) {
            this.cmd = cmd;
            this.workingDirectory = workingDirectory;
        }
    
        public void start() {
            WinBase.STARTUPINFO si = new WinBase.STARTUPINFO();
            si.clear();
    
            pi = new WinBase.PROCESS_INFORMATION.ByReference();
            pi.clear();
    
            Kernel32.JOBJECT_EXTENDED_LIMIT_INFORMATION jeli = new Kernel32.JOBJECT_EXTENDED_LIMIT_INFORMATION.ByReference();
            jeli.clear();
    
            Kernel32.JOBOBJECT_BASIC_UI_RESTRICTIONS uli = new Kernel32.JOBOBJECT_BASIC_UI_RESTRICTIONS.ByReference();
            uli.clear();
    
            // Call SetHandleInformation. Take a look in SocketLock.cs
    
            hJob = kernel32.CreateJobObject(null, null);
            if (hJob.getPointer() == null) {
                throw new RuntimeException("Cannot create job object: " + kernel32.GetLastError());
            }
    
            // Hopefully, Windows will kill the job automatically if this process dies
            // But beware! Process Explorer can break this by keeping open a handle to all jobs!
            // http://forum.sysinternals.com/forum_posts.asp?TID=4094
            jeli.BasicLimitInformation.LimitFlags = Kernel32.JOB_OBJECT_LIMIT_BREAKAWAY_OK | Kernel32.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    
            if (!kernel32.SetInformationJobObject(hJob, Kernel32.JobObjectExtendedLimitInformation, jeli.getPointer(), jeli.size())) {
                throw new RuntimeException("Unable to set extended limit information on the job object: " + kernel32.GetLastError());
            }
    
            // crete job in sandbox with own global atom table
            uli.UIRestrictionsClass = Kernel32.JOB_OBJECT_UILIMIT_GLOBALATOMS;
    
            if (!kernel32.SetInformationJobObject(hJob, Kernel32.JobObjectBasicUIRestrictions, uli.getPointer(), uli.size())) {
                throw new RuntimeException("Unable to set ui limit information on the job object: " + kernel32.GetLastError());
            }
    
            WinDef.DWORD creationFlags = new WinDef.DWORD(Kernel32.CREATE_SUSPENDED | // Suspend so we can add to job
                    Kernel32.CREATE_BREAKAWAY_FROM_JOB | // Allow ourselves to breakaway from Vista's PCA if necessary
                    Kernel32.CREATE_NEW_PROCESS_GROUP);
    
            // Start the child process
            boolean result = kernel32.CreateProcess(null, // No module name (use command line).
                    cmd, // Command line.
                    null, // Process handle not inheritable.
                    null, // Thread handle not inheritable.
                    false, // Set handle inheritance to FALSE.
                    creationFlags, // Set creation flags
                    null, // Use parent's environment block.
                    workingDirectory, // Use provided working directory, parent's directory if null.
                    si, // Pointer to STARTUPINFO structure.
                    pi); // Pointer to PROCESS_INFORMATION structure.
            if (!result) {
                throw new RuntimeException("Failed to create the process: " + kernel32.GetLastError());
            }
    
            if (!kernel32.AssignProcessToJobObject(hJob, pi.hProcess)) {
                throw new RuntimeException("Cannot assign process to job: " + kernel32.GetLastError());
            }
    
            if (kernel32.ResumeThread(pi.hThread) <= 0) {
                throw new RuntimeException("Cannot resume thread: " + kernel32.GetLastError());
            }
    
            kernel32.CloseHandle(pi.hThread);
            // Kernel32.CloseHandle(pi.hProcess);
        }
    
        public boolean isRunning() {
            return hJob != null;
        }
    
        public void destroy() {
            if (!isRunning()) {
                return;
            }
    
            kernel32.CloseHandle(pi.hProcess);
            pi = null;
    
            // This seems a trifle brutal. Oh well. Brutal it is.
            kernel32.TerminateJobObject(hJob, 666);
            kernel32.CloseHandle(hJob);
            hJob = null;
    
        }
    
        public int waitFor() {
            if (isRunning()) {
                kernel32.WaitForSingleObject(pi.hProcess, Kernel32.INFINITE);
                IntByReference exitCode = new IntByReference();
                if (kernel32.GetExitCodeProcess(pi.hProcess, exitCode)) {
                    return exitCode.getValue();
                }
                destroy();
            }
            return -1;
        }
    
    }
    

    为了测试它,我刚刚创建了这个演示:

    public class Demo {
    
        public static void main(String[] args) {
            ProcessChildAttached proc = new ProcessChildAttached("my-child-process.exe", ".");
    
            proc.start();
    
            System.out.println("Now kill this java.exe and child process will die too");
    
            try (Scanner sc = new Scanner(System.in)) {
                System.out.println("And and destroy child process? [ y/N ]");
                String option = sc.nextLine();
                if (!"Y".equalsIgnoreCase(option)) {
                    return;
                }
            }
    
            proc.destroy();
        }
    
    }
    

    问题是 JobObject 在我的应用程序中创建(在进程内),但不存在限制,如果我杀死父进程(Java exe),子进程将继续运行。

    查看进程(Java.exe)属性:

    Job wihtout limits

    预期情况如下(Spotify流程示例):

    Spotify Job limits are correct

    在Lazarus中使用相同的代码(free-pascal),一切都会很顺利:

    uses 
       jwawinbase, JwaWinNT
    
    var
      fProcessInfo: TProcessInformation;
      fJob: Cardinal;
    
    procedure Start();
    var
      si: TStartupInfo;
      limits : TJobObjectExtendedLimitInformation;
      ui: TJobObjectBasicUiRestrictions;
      createFlags: integer;
    begin
      FillChar(fProcessInfo, SizeOf(fProcessInfo), 0);
      fJob := 0;
    
      fJob := CreateJobObject(nil, nil);
      if fJob = 0 then
        RaiseLastOSError;
    
      FillChar(Limits,SizeOf(Limits),0);
      with Limits,BasicLimitInformation do
        LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE or JOB_OBJECT_LIMIT_BREAKAWAY_OK;
    
      if not SetInformationJobObject(fJob, JobObjectExtendedLimitInformation, @limits, SizeOf(limits)) then
        RaiseLastOSError;
    
      FillChar(ui, SizeOf(ui), 0);
      ui.UIRestrictionsClass := JOB_OBJECT_UILIMIT_GLOBALATOMS;
    
      if not SetInformationJobObject(fJob, JobObjectBasicUIRestrictions, @ui, SizeOf(ui)) then
        RaiseLastOSError;
    
      Fillchar(si, SizeOf(si), 0);
      si.cb := SizeOf(si);
    
      createFlags := CREATE_SUSPENDED or CREATE_BREAKAWAY_FROM_JOB or NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP;
    
      if not CreateProcess(nil, PChar('notepad.exe'), nil, nil, false, createFlags, nil, nil, si, fProcessInfo) then
        RaiseLastOSError;
    
      if not AssignProcessToJobObject(fJob, fProcessInfo.hProcess) then
        RaiseLastOSError;
    
      if ResumeThread(fProcessInfo.hThread) = $FFFFFFFF then
        RaiseLastOSError;
    end;
    

    有人可以帮助我解决这个问题在我的Java应用程序吗?

    1 回复  |  直到 6 年前
        1
  •  1
  •   Beto Neto    6 年前

    解决了的!

    我需要 write the fields to memory 在通过之前 Structures 方法调用。

    这是最终代码(搜索注释 // <<<< WRITE THE FIELDS TO NATIVE MEMORY ):

    import java.util.Arrays;
    import java.util.List;
    import java.util.Scanner;
    import java.util.stream.Collectors;
    
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    import com.sun.jna.platform.win32.WinBase;
    import com.sun.jna.platform.win32.WinDef;
    import com.sun.jna.platform.win32.WinNT;
    import com.sun.jna.platform.win32.WinNT.HANDLE;
    import com.sun.jna.ptr.IntByReference;
    import com.sun.jna.win32.W32APIOptions;
    
    public final class ProcessChildAttached {
    
        public static abstract class Structure extends com.sun.jna.Structure {
    
            public Structure() {
                super();
            }
    
            public Structure(Pointer p) {
                super(p);
            }
    
            private List<String> fields;
    
            protected Class<? extends Structure> getFieldsClass() {
                Class<? extends Structure> ret = this.getClass();
                if (ByReference.class.isAssignableFrom(ret) && com.sun.jna.Structure.class.isAssignableFrom(ret.getSuperclass())) {
                    ret = (Class<? extends Structure>) ret.getSuperclass();
                }
                return ret;
            }
    
            @Override
            protected List<String> getFieldOrder() {
                if (fields == null) {
                    fields = Arrays.stream(getFieldsClass().getDeclaredFields()).map(df -> df.getName()).collect(Collectors.toList());
                }
                return fields;
            }
    
        }
    
        static interface Kernel32 extends com.sun.jna.platform.win32.Kernel32 {
    
            Kernel32 INSTANCE = Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
    
            WinNT.HANDLE CreateJobObject(WinBase.SECURITY_ATTRIBUTES attrs, String name);
    
            boolean SetInformationJobObject(HANDLE hJob, int JobObjectInfoClass, Pointer lpJobObjectInfo, int cbJobObjectInfoLength);
    
            boolean AssignProcessToJobObject(HANDLE hJob, HANDLE hProcess);
    
            boolean TerminateJobObject(HANDLE hJob, long uExitCode);
    
            int ResumeThread(HANDLE hThread);
    
            // 0x00000800
            int JOB_OBJECT_LIMIT_BREAKAWAY_OK = 2048;
    
            // 0x00002000
            int JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 8192;
    
            // see SetInformationJobObject at msdn
            int JobObjectExtendedLimitInformation = 9;
    
            // see SetInformationJobObject at msdn
            int JobObjectBasicUIRestrictions = 4;
    
            // 0x00000020
            int JOB_OBJECT_UILIMIT_GLOBALATOMS = 0x00000020;
    
            // 0x00000004
            int CREATE_SUSPENDED = 4;
    
            // 0x01000000
            int CREATE_BREAKAWAY_FROM_JOB = 16777216;
    
            static class JOBJECT_BASIC_LIMIT_INFORMATION extends Structure {
                public LARGE_INTEGER PerProcessUserTimeLimit;
                public LARGE_INTEGER PerJobUserTimeLimit;
                public int LimitFlags;
                public SIZE_T MinimumWorkingSetSize;
                public SIZE_T MaximumWorkingSetSize;
                public int ActiveProcessLimit;
                public ULONG_PTR Affinity;
                public int PriorityClass;
                public int SchedulingClass;
            }
    
            static class IO_COUNTERS extends Structure {
    
                public ULONGLONG ReadOperationCount;
                public ULONGLONG WriteOperationCount;
                public ULONGLONG OtherOperationCount;
                public ULONGLONG ReadTransferCount;
                public ULONGLONG WriteTransferCount;
                public ULONGLONG OtherTransferCount;
    
            }
    
            static class JOBJECT_EXTENDED_LIMIT_INFORMATION extends Structure {
    
                public JOBJECT_EXTENDED_LIMIT_INFORMATION() {
                }
    
                public JOBJECT_EXTENDED_LIMIT_INFORMATION(Pointer memory) {
                    super(memory);
                }
    
                public JOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
                public IO_COUNTERS IoInfo;
                public SIZE_T ProcessMemoryLimit;
                public SIZE_T JobMemoryLimit;
                public SIZE_T PeakProcessMemoryUsed;
                public SIZE_T PeakJobMemoryUsed;
    
                public static class ByReference extends JOBJECT_EXTENDED_LIMIT_INFORMATION implements Structure.ByReference {
    
                    public ByReference() {
                    }
    
                    public ByReference(Pointer memory) {
                        super(memory);
                    }
                }
            }
    
            static class JOBOBJECT_BASIC_UI_RESTRICTIONS extends Structure {
                public JOBOBJECT_BASIC_UI_RESTRICTIONS() {
                }
    
                public JOBOBJECT_BASIC_UI_RESTRICTIONS(Pointer memory) {
                    super(memory);
                }
    
                public int UIRestrictionsClass;
    
                public static class ByReference extends JOBOBJECT_BASIC_UI_RESTRICTIONS implements Structure.ByReference {
                    public ByReference() {
                    }
    
                    public ByReference(Pointer memory) {
                        super(memory);
                    }
                }
            }
    
        }
    
        private Kernel32 kernel32 = Kernel32.INSTANCE;
    
        private String cmd;
        private String workingDirectory;
        private HANDLE hJob;
        private WinBase.PROCESS_INFORMATION.ByReference pi;
    
        public ProcessChildAttached(String cmd, String workingDirectory) {
            this.cmd = cmd;
            this.workingDirectory = workingDirectory;
        }
    
        public void start() {
            WinBase.STARTUPINFO si = new WinBase.STARTUPINFO();
            si.clear();
    
            pi = new WinBase.PROCESS_INFORMATION.ByReference();
            pi.clear();
    
            Kernel32.JOBJECT_EXTENDED_LIMIT_INFORMATION jeli = new Kernel32.JOBJECT_EXTENDED_LIMIT_INFORMATION.ByReference();
            jeli.clear();
    
            Kernel32.JOBOBJECT_BASIC_UI_RESTRICTIONS uli = new Kernel32.JOBOBJECT_BASIC_UI_RESTRICTIONS.ByReference();
            uli.clear();
    
            // Call SetHandleInformation. Take a look in SocketLock.cs
    
            hJob = kernel32.CreateJobObject(null, null);
            if (hJob.getPointer() == null) {
                throw new RuntimeException("Cannot create job object: " + kernel32.GetLastError());
            }
    
            // Hopefully, Windows will kill the job automatically if this process dies
            // But beware! Process Explorer can break this by keeping open a handle to all jobs!
            // http://forum.sysinternals.com/forum_posts.asp?TID=4094
            jeli.BasicLimitInformation.LimitFlags = Kernel32.JOB_OBJECT_LIMIT_BREAKAWAY_OK | Kernel32.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    
            jeli.write(); // <<<< WRITE THE FIELDS TO NATIVE MEMORY
    
            if (!kernel32.SetInformationJobObject(hJob, Kernel32.JobObjectExtendedLimitInformation, jeli.getPointer(), jeli.size())) {
                throw new RuntimeException("Unable to set extended limit information on the job object: " + kernel32.GetLastError());
            }
    
            // crete job in sandbox with own global atom table
            uli.UIRestrictionsClass = Kernel32.JOB_OBJECT_UILIMIT_GLOBALATOMS;
    
            uli.write(); // <<<< WRITE THE FIELDS TO NATIVE MEMORY
    
            if (!kernel32.SetInformationJobObject(hJob, Kernel32.JobObjectBasicUIRestrictions, uli.getPointer(), uli.size())) {
                throw new RuntimeException("Unable to set ui limit information on the job object: " + kernel32.GetLastError());
            }
    
            WinDef.DWORD creationFlags = new WinDef.DWORD(Kernel32.CREATE_SUSPENDED | // Suspend so we can add to job
                    Kernel32.CREATE_BREAKAWAY_FROM_JOB | // Allow ourselves to breakaway from Vista's PCA if necessary
                    Kernel32.CREATE_NEW_PROCESS_GROUP);
    
            // Start the child process
            boolean result = kernel32.CreateProcess(null, // No module name (use command line).
                    cmd, // Command line.
                    null, // Process handle not inheritable.
                    null, // Thread handle not inheritable.
                    false, // Set handle inheritance to FALSE.
                    creationFlags, // Set creation flags
                    null, // Use parent's environment block.
                    workingDirectory, // Use provided working directory, parent's directory if null.
                    si, // Pointer to STARTUPINFO structure.
                    pi); // Pointer to PROCESS_INFORMATION structure.
            if (!result) {
                throw new RuntimeException("Failed to create the process: " + kernel32.GetLastError());
            }
    
            if (!kernel32.AssignProcessToJobObject(hJob, pi.hProcess)) {
                throw new RuntimeException("Cannot assign process to job: " + kernel32.GetLastError());
            }
    
            if (kernel32.ResumeThread(pi.hThread) <= 0) {
                throw new RuntimeException("Cannot resume thread: " + kernel32.GetLastError());
            }
    
            kernel32.CloseHandle(pi.hThread);
            // Kernel32.CloseHandle(pi.hProcess);
        }
    
        public boolean isRunning() {
            return hJob != null;
        }
    
        public void destroy() {
            if (!isRunning()) {
                return;
            }
    
            kernel32.CloseHandle(pi.hProcess);
            pi = null;
    
            // This seems a trifle brutal. Oh well. Brutal it is.
            kernel32.TerminateJobObject(hJob, 666);
            kernel32.CloseHandle(hJob);
            hJob = null;
    
        }
    
        public int waitFor() {
            if (isRunning()) {
                kernel32.WaitForSingleObject(pi.hProcess, Kernel32.INFINITE);
                IntByReference exitCode = new IntByReference();
                if (kernel32.GetExitCodeProcess(pi.hProcess, exitCode)) {
                    return exitCode.getValue();
                }
                destroy();
            }
            return -1;
        }
    
    }