我想启动一个进程,使得JVM可能会死,但即使它正在写入STDOUT,生成的进程也会继续运行。
我首先尝试使用ProcessBuilder
并将输出设置为Files并传入:
cmd /c myCmd.exe arg0 arg1
然而,即使在关闭所有输入/输出流之后,如果我调用Process#.waitFor
,则在myCmd.exe
完成之前它不会返回。它似乎仍以某种方式连接到JVM(即使JVM可能在此时死亡并且不会影响子proc)。
然后我尝试了start
命令,似乎不在路径上(我找不到c:\windows
中的bin)所以我在cmd
参数下运行它(传递给ProcessBuilder
的空格分隔成了:
cmd /c start /b myCmd.exe arg0 arg2 >log 2>&1
结果是:
Process#.waitFor
在myCmd.exe
完成之前返回。ProcessBuilder
的其他日志文件echo
并且参数为^^^^\foo
它将写入日志文件^\foo
,则转义变得奇怪,我也注意到我是否给了它"^^^^\foo"
它会返回相同的内容,即"^^^^\foo"
。所以:
cmd.exe /c start /b
做正确的事吗?cmd.exe
调用start
而做一些不同的事情,或许我需要真正逃脱某种程度上来说?也许我不理解Windows进程他们甚至对获取一系列参数有适当的支持吗?C
调用本机库?如果是这样的话,我不介意如果必须调用C
程序让我的进程在后台运行。答案 0 :(得分:0)
我认为在后台进程中创建一个分离的解决方案,JVM没有引用它也支持以理智的方式传递任何参数的可能性,它是使用CreateProcess
API。为此我用过:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>platform</artifactId>
<version>3.5.0</version>
</dependency>
(这是旧版本,但它恰好已经在使用中)。
让它运行的Jave代码是:
/**
*
* @param command a pre escaped command line e.g. c:\perl.exe c:\my.pl arg
* @param env A non null environment.
* @param stdoutFile
* @param stderrFile
* @param workingDir
*/
public void execute(String command, Map<String, String> env,
File stdoutFile, File stderrFile, String workingDir) {
WinBase.SECURITY_ATTRIBUTES sa = new WinBase.SECURITY_ATTRIBUTES();
sa.bInheritHandle = true; // I think the child processes gets handles I make with
// with this sa.
sa.lpSecurityDescriptor = null; // Use default access token from current proc.
HANDLE stdout = makeFileHandle(sa, stdoutFile);
HANDLE stderr = null;
if(stderrFile != null &&
!stderrFile.getAbsolutePath().equals(stdoutFile.getAbsolutePath())) {
stderr = makeFileHandle(sa, stderrFile);
}
try {
WinBase.STARTUPINFO si = new WinBase.STARTUPINFO();
// Assume si.cb is set by the JVM.
si.dwFlags |= WinBase.STARTF_USESTDHANDLES;
si.hStdInput = null; // No stdin for the child.
si.hStdOutput = stdout;
si.hStdError = Optional.ofNullable(stderr).orElse(stdout);
DWORD dword = new DWORD();
dword.setValue(WinBase.CREATE_UNICODE_ENVIRONMENT | // Probably makes sense.
WinBase.CREATE_NO_WINDOW | // Well we don't want a window so this makes sense.
WinBase.DETACHED_PROCESS); // I think this would let the JVM die without stopping the child
// Newer versions of platform don't use a reference.
WinBase.PROCESS_INFORMATION.ByReference processInfoByRef = new WinBase.PROCESS_INFORMATION.ByReference();
boolean result = Kernel32.INSTANCE
.CreateProcess(null, // use next argument to get the task to run
command,
null, // Don't let the child inherit a handle to itself, because I saw someone else do it.
null, // Don't let the child inherit a handle to its own main thread, because I saw someone else do it.
true, // Must be true to pass any handle to the spawned thread including STDOUT and STDERR
dword,
asPointer(createEnvironmentBlock(env)), // I hope that the new processes copies this memory
workingDir,
si,
processInfoByRef);
// Put code in try block.
try {
// Did it start?
if(!result) {
throw new RuntimeException("Could not start command: " + command);
}
} finally {
// Apparently both parent and child need to close the handles.
Kernel32.INSTANCE.CloseHandle(processInfoByRef.hProcess);
Kernel32.INSTANCE.CloseHandle(processInfoByRef.hThread);
}
} finally {
// We need to close this
// https://stackoverflow.com/questions/6581103/do-i-have-to-close-inherited-handle-later-owned-by-child-process
Kernel32.INSTANCE.CloseHandle(stdout);
if(stderr != null) {
Kernel32.INSTANCE.CloseHandle(stderr);
}
}
}
private HANDLE makeFileHandle(WinBase.SECURITY_ATTRIBUTES sa, File file) {
return Kernel32.INSTANCE
.CreateFile(file.getAbsolutePath(),
Kernel32.FILE_APPEND_DATA, // IDK I saw this in an example.
Kernel32.FILE_SHARE_WRITE | Kernel32.FILE_SHARE_READ,
sa,
Kernel32.OPEN_ALWAYS,
Kernel32.FILE_ATTRIBUTE_NORMAL,
null);
}
public static byte[] createEnvironmentBlock(Map<String, String> env) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// This charset seems to work.
Charset charset = StandardCharsets.UTF_16LE;
try {
for(Entry<String, String> entry : env.entrySet()) {
bos.write(entry.getKey().getBytes(charset));
bos.write("=".getBytes(charset));
bos.write(entry.getValue().getBytes(charset));
bos.write(0);
bos.write(0);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
bos.write(0);
bos.write(0);
return bos.toByteArray();
}
public static Pointer asPointer(byte[] data) {
Pointer pointer = new Memory(data.length);
pointer.write(0, data, 0, data.length);
return pointer;
}
ProcessBuilder
的STDOUT / STDERR,后来又需要重新定位实际运行的命令。"
这样的东西,我无法知道如何使用ProcessBuilder
cmd /c start /b command
ProcessBuilder
。似乎JVM正在进行一些转义,因此可能无法构造所需的字符串来获得正确的命令。cmd /c
和{{1 }}。cmd.exe