我需要在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)属性:
预期情况如下(Spotify流程示例):
在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应用程序吗?