Как запустить команду в окнах под Java, который полностью отключен - PullRequest
0 голосов
/ 24 мая 2018

Я хочу запустить процессы так, чтобы JVM могла умереть, но порожденные процессы продолжали выполняться, даже если он записывает в STDOUT.

Сначала я попытался использовать ProcessBuilder с выходом, установленным в Filesи передавая:

cmd /c myCmd.exe arg0 arg1

Однако даже после закрытия всех потоков ввода / вывода, если я вызову Process#.waitFor, он не вернется, пока не закончится myCmd.exe.Кажется, он все еще каким-то образом подключен к JVM (хотя JVM, вероятно, может умереть в этот момент и не повлиять на дочерний процесс).

Затем я попробовал команду start, похоже, этоне на пути (я не смог найти корзину в c:\windows), поэтому я запустил ее под cmd аргументы (разделенные пробелом), передаваемые ProcessBuilder, стали:

cmd /c start /b myCmd.exe arg0 arg2 >log 2>&1

Это приводитв:

  • Process#.waitFor возвращение до завершения myCmd.exe.
  • seemed Казалось, что мне нужно было использовать другой файл журнала из файла, переданного в ProcessBuilder
  • then Затем я обнаружил, что экранирование становится странным, если команда запускалась echo, а аргумент был ^^^^\foo, это записывало бы в файл журнала ^\foo, я также заметил, что если бы дал "^^^^\foo"вернул бы то же самое, то есть "^^^^\foo".

Итак:

  1. Является ли вызов cmd.exe /c start /b правильным поступком?
  2. Я делаючто-то не так с выходом (что я и даю сборщику процессов), если я, возможно, буду делать что-то другоеарендовать из-за cmd.exe звонка start, может быть, мне нужно каким-то образом сбежать?Возможно, я не понимаю процессы Windows, они даже имеют надлежащую поддержку для принятия массива аргументов?
  3. Я поступаю неправильно, если я пытаюсь вызвать нативную библиотеку из C?Если это так, что бы я не возражал, если бы мне пришлось вызывать программу C, чтобы запустить мой процесс в фоновом режиме.

1 Ответ

0 голосов
/ 25 мая 2018

Я думаю, что решение для создания отдельного фонового процесса, на который JVM не ссылается, также поддерживает возможность передавать любые аргументы в здравом смысле, если использовать API CreateProcess.Для этого я использовал:

  <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
        // /7593106/nuzhno-li-zakryvat-unasledovannyi-deskriptor-prinadlezhaschii-potom-dochernemu-protsessu
        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;
}
  • ✓ Кажется, что запущенный процесс продолжает работать, даже если JVM остановлена.
  • ✓ Мне не нужно было иметь дело с STDOUT / STDERR с ProcessBuilder, а затем еще одним.от необходимости перенаправить команду на самом деле запустить.
  • ✓ Если вы можете правильно экранировать свою команду (код для выполнения которого отсутствует в ответе, поскольку он не мой для совместного использования), вы можете передать такие вещи, как ", которыйЯ не мог понять, как это сделать при использовании ProcessBuilder с cmd /c start /b command.Казалось, что JVM делает какое-то побег, делая невозможным создание необходимой строки для получения правильной команды.
  • ✓ Я мог видеть, что дескрипторы файлов, удерживаемые JVM для stdout / stderr, освобождаются до завершения процесса.
  • ✓ Я мог бы создать 13 тыс. Задач без JVM, генерирующей OOM с 62 МБ памяти, выделенной JVM (похоже, что JVM не удерживает ресурсы, как некоторые люди в конечном итоге просто делают ProcessBuilder и cmd /c.
  • ✓ промежуточное звено cmd.exe не создано
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...