Завершение работы Windows при запуске приложения Java из bat script

У меня есть bat script, который запускает java-приложение. Если я нажму на него ctrl + c, это приложение прекратится изящно, вызывая все выключения. Однако, если я просто закрываю окно cmd bat script, крючки отключения никогда не вызывают.

Есть ли способ решить это? Возможно, есть способ сообщить летучей мыши script, как прекратить вызываться приложения, когда его окно закрыто?

Ответы

Ответ 1

Из addShutdownHook документация:

В редких случаях виртуальная машина может прервать работу, то есть прекратить работу, не выключая ее. Это происходит, когда виртуальная машина завершается снаружи, например, с сигналом SIGKILL в Unix или вызовом TerminateProcess в Microsoft Windows.

Так что, к сожалению, мне здесь нечего делать.


CTRL-CLOSE в консоли Windows. Кажется непривлекательным.

Цитата выше:

Система генерирует сигнал CTRL+CLOSE, когда пользователь закрывает консоль. Все процессы, подключенные к консоли, получают сигнал, давая каждому процессу возможность очистить до завершения. Когда процесс получает этот сигнал, функция обработчика может выполнить одно из следующих действий после выполнения любых операций очистки:

  • Вызовите ExitProcess, чтобы завершить процесс.
  • Возврат FALSE. Если ни одна из зарегистрированных функций обработчика не возвращает TRUE, обработчик по умолчанию завершает процесс.
  • Возврат TRUE. В этом случае не вызываются никакие другие функции обработчика, а всплывающее диалоговое окно запрашивает у пользователя, прекратить ли процесс. Если пользователь решает не прекращать процесс, система не закрывает консоль до тех пор, пока процесс не завершится окончательно.

UPD. Если для вас приемлемы собственные твики, функция WinAPI SetConsoleCtrlHandler открывает путь для подавления поведения по умолчанию.

UPD2. Откровения по обработке и завершению обработки сигналов Java относительно старой статьи, но раздел Написание обработчиков сигналов Java действительно может содержать то, что вам нужно.


UPD3. Я пробовал обработчики сигналов Java из статьи выше. Он работает с SIGINT красиво, но это не то, что нам нужно, и я решил нести его с помощью SetConsoleCtrlHandler. Результат немного сложный и может не стоить реализовывать в вашем проекте. В любом случае, это может помочь кому-то другому.

Итак, идея заключалась в следующем:

  • Сохраняйте ссылку на поток обработчика выключения.
  • Установить пользовательскую процедуру обработчика собственных консолей с помощью JNI.
  • Вызовите настраиваемый метод Java на CTRL+CLOSE сигнале.
  • Обработчик выключения вызова из этого метода.

Код Java:

public class TestConsoleHandler {

    private static Thread hook;

    public static void main(String[] args) {
        System.out.println("Start");
        hook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(hook);
        replaceConsoleHandler(); // actually not "replace" but "add"

        try {
            Thread.sleep(10000); // You have 10 seconds to close console
        } catch (InterruptedException e) {}
    }

    public static void shutdown() {
        hook.run();
    }

    private static native void replaceConsoleHandler();

    static {
        System.loadLibrary("TestConsoleHandler");
    }
}

class ShutdownHook extends Thread {
    public void run() {
        try {
            // do some visible work
            new File("d:/shutdown.mark").createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Shutdown");
    }
}

Нативный replaceConsoleHandler:

JNIEXPORT void JNICALL Java_TestConsoleHandler_replaceConsoleHandler(JNIEnv *env, jclass clazz) {
    env->GetJavaVM(&jvm);
    SetConsoleCtrlHandler(&HandlerRoutine, TRUE);
}

И сам обработчик:

BOOL WINAPI HandlerRoutine(__in DWORD dwCtrlType) {
    if (dwCtrlType == CTRL_CLOSE_EVENT) {
        JNIEnv *env;
        jint res =  jvm->AttachCurrentThread((void **)(&env), &env);
        jclass cls = env->FindClass("TestConsoleHandler");
        jmethodID mid = env->GetStaticMethodID(cls, "shutdown", "()V");
        env->CallStaticVoidMethod(cls, mid);
        jvm->DetachCurrentThread();
        return TRUE;
    }
    return FALSE;
}

И это работает. В коде JNI все проверки ошибок опущены для разрешения. Обработчик завершения работы создает пустой файл "d:\shutdown.mark" для указания правильного завершения работы.

Полные источники с скомпилированными тестовыми двоичными файлами здесь.

Ответ 2

В дополнение к вышеуказанному ответу на использование SetConsoleCtrlHandler вы также можете сделать это с помощью JNA, а не писать собственный собственный код.

Вы можете создать свой собственный интерфейс на kernel32, если хотите, или использовать тот, который предоставлен в этой превосходной структуре: https://gitlab.com/axet/desktop p >

Пример кода:

import com.github.axet.desktop.os.win.GetLastErrorException;
import com.github.axet.desktop.os.win.handle.HANDLER_ROUTINE;
import com.github.axet.desktop.os.win.libs.Kernel32Ex;
...
private static HANDLER_ROUTINE handler =
  new HANDLER_ROUTINE()
  {
    @Override
    public long callback(long dwCtrlType) {
      if ((int)dwCtrlType == CTRL_CLOSE_EVENT) {
        // *** do your shutdown code here ***
        return 1;
      }
      return 0;
    }
  };

public static void assignShutdownHook() {
  if (!Kernel32Ex.INSTANCE.SetConsoleCtrlHandler(handler, true))
    throw new GetLastErrorException();
}

Обратите внимание, что я назначил анонимный класс в поле. Я изначально определил его в вызове SetConsoleCtrlHandler, но я думаю, что он собирался с помощью JVM.

Редактировать 4/9/17: Обновлена ​​ссылка из github на gitlab.

Ответ 3

Хотя командный файл может быть завершен, консоль (окно), в котором запущен пакетный файл, может быть оставлена ​​открытой, в зависимости от операционной системы, командного процессора и способа запуска выполнения пакетного файла (из командной строки или через ярлык).

taskkill - хорошая команда для завершения программы, которая распространяется с Windows (я предполагаю, что вы хотите остановить DIFFERENT-программу, а не сам пакетный файл).

Можно завершить программы с помощью идентификатора процесса, имени исполняемого файла, заголовка окна, статуса (то есть не отвечающего), имени DLL или имени службы.

Вот несколько примеров, основанных на том, как вы можете использовать это в своем пакетном файле:

Force "program.exe" для остановки (часто требуется флаг /f "force", чтобы заставить программу остановиться, просто проверьте, необходимо ли это для вашего приложения путем проб и ошибок):

taskkill /f /im program.exe 

Остановите любые невосприимчивые программы:

taskkill /fi "Status eq NOT RESPONDING"

Остановить программу на основе ее заголовка окна (* подстановочный знак разрешен здесь, чтобы соответствовать чему-либо):

taskkill /fi "WindowTitle eq Please Login"

taskkill /fi "WindowTitle eq Microsoft*"

Вы даже можете использовать его, чтобы остановить программу на другом компьютере в вашей сети (хотя запись пароля учетной записи в пакетном файле просто не очень хорошая идея).

taskkill /s JimsPC /u Jim /p James_007 /im firefox.exe

Другая альтернатива, также распространяемая с Windows, - tskill. Хотя у него не так много вариантов, команды немного проще.

EDIT:

Я не уверен, что есть. Я думаю, что закрытие окна cmd похоже на force-closing приложение, то есть немедленное завершение без дальнейшего уведомления. У этого есть поведение многих приложений, которые, когда их просят force-close, на самом деле они занимают много времени, чтобы окончательно завершить работу. Это связано с тем, что в ОС, чтобы освободить все ресурсы, некоторые ресурсы (особенно определенные I/O и/или файловые ресурсы) сразу не отпускаются.

IMO, там ничто не может сделать Java, если захочет. На уровне ОС происходит принудительное закрытие, и в этот момент он освобождает используемую память, файлы и дескрипторы ввода-вывода и т.д. Java не имеет (и не выполняет какой-либо другой программы) контроль над закрытием силы. На данный момент ОС берет на себя ответственность.

Кто-то, пожалуйста, поправьте меня, если я ошибаюсь.