且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-02-15 23:13:28

已解决!

我需要

I need to write the fields to memory before pass the Structures to the methods call.

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

This is the final code (search for comment // <<<< 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;
    }

}