【问题标题】:Windows shutdown hook on java application run from a bat script从 bat 脚本运行的 java 应用程序上的 Windows 关闭挂钩
【发布时间】:2012-03-05 20:45:21
【问题描述】:

我有一个运行 java 应用程序的 bat 脚本。如果我在它上面按 ctrl+c,应用程序会优雅地终止,调用所有的关闭挂钩。但是,如果我只是关闭 bat 脚本的 cmd 窗口,则永远不会调用关闭挂钩。

有没有办法解决这个问题?也许有一种方法可以告诉 bat 脚本在其窗口关闭时如何终止调用的应用程序?

【问题讨论】:

    标签: java windows batch-file application-shutdown shutdown-hook


    【解决方案1】:

    除了上述使用 SetConsoleCtrlHandler 的答案之外,您还可以使用 JNA 来执行此操作,而不是编写自己的本机代码。

    如果您愿意,您可以在 kernel32 上创建自己的接口,或者使用这个优秀框架中提供的接口:https://gitlab.com/axet/desktop

    一些示例代码:

    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 的链接。

    【讨论】:

    • 您的方法对我不起作用(Windows 10、Java 1.8)...该挂钩在 Windows 控制台中的 CTRL+C 上不起作用...
    • CTRL_CLOSE_EVENT 仅在您强制关闭命令窗口时触发。 CTRL+C 将优雅地结束 bat/cmd 文件并且不会触发该事件。如果您想处理干净的关机,请使用Runtime.addShutdownHook(Thread hook)docs.oracle.com/javase/8/docs/api/java/lang/…
    【解决方案2】:

    来自addShutdownHook 文档:

    在极少数情况下,虚拟机可能会中止,即在没有完全关闭的情况下停止运行。当虚拟机在外部终止时会发生这种情况,例如 Unix 上的 SIGKILL 信号或 Microsoft Windows 上的 TerminateProcess 调用。

    不幸的是,我觉得这里没什么可做的。


    CTRL-CLOSE Windows 控制台中的信号。似乎不可调整。

    引用以上链接:

    当用户关闭控制台时,系统会生成CTRL+CLOSE 信号。连接到控制台的所有进程都接收到信号,从而使每个进程都有机会在终止之前进行清理。当进程收到此信号时,处理函数可以在执行任何清理操作后采取以下操作之一:

    • 调用ExitProcess终止进程。
    • 返回FALSE。如果已注册的处理程序函数均未返回TRUE,则默认处理程序将终止进程。
    • 返回TRUE。在这种情况下,不会调用其他处理函数,并且会弹出一个对话框询问用户是否终止进程。如果用户选择不终止进程,系统不会关闭控制台,直到进程最终终止。

    UPD。如果您可以接受本机调整,WinAPI SetConsoleCtrlHandler 函数打开了抑制默认行为的方式。

    UPD2Revelations on Java signal handling and termination 比较老的文章,但是Writing Java signal handlers部分确实可能包含你需要的东西。


    UPD3。 我已经尝试了上面文章中的 Java 信号处理程序。它可以很好地与SIGINT 一起使用,但这不是我们需要的,我决定将它与SetConsoleCtrlHandler 一起使用。结果有点复杂,可能不值得在您的项目中实施。无论如何,它可以帮助别人。

    所以,这个想法是:

    1. 保留对关闭处理程序线程的引用。
    2. 使用 JNI 设置自定义本机控制台处理程序例程。
    3. CTRL+CLOSE 信号上调用自定义Java 方法。
    4. 从该方法调用关闭处理程序。

    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" 以指示正确关机。

    带有已编译测试二进制文件的完整源代码here

    【讨论】:

    • 是的,我已经读过了。我只是想知道批处理文件中是否存在某种关闭挂钩。
    • 我已经更新了答案。也许有人会建议控制台使用一些魔法。
    • 再次更新,附有 Chris White 文章的链接。
    • 这似乎不是真的,因为当我按 ctrl+c 停止我的批处理脚本时,会调用关闭挂钩,但是当我只是关闭窗口时,它们不是。
    • Ctrl-C 产生不同的信号。目前我无法检查文章中描述的解决方案,但我现在很担心,很快就会深入了解。
    【解决方案3】:

    虽然批处理文件可能会终止,但批处理文件已在其中运行的控制台(窗口)可能会保持打开状态,具体取决于操作系统、命令处理器以及批处理文件执行的启动方式(从命令提示符或通过快捷方式)。

    taskkill 是结束随 Windows 分发的程序的好命令(我假设您要停止不同的程序,而不是批处理文件本身)。

    可以通过进程 ID、可执行文件名称、窗口标题、状态(即未响应)、DLL 名称或服务名称来结束程序。

    以下是一些基于您如何在批处理文件中使用它的示例:

    强制“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。虽然它没有那么多选项,但命令要简单一些。

    编辑:

    我不确定是否有。我认为关闭 cmd 窗口类似于force-closing 应用程序,即立即终止,无需另行通知。这具有许多应用程序的行为,当它们被要求force-close 时,它们实际上需要很长时间才能最终终止。这是因为在操作系统释放所有资源的努力中,有些资源(尤其是某些 I/O 和/或文件资源)并没有立即释放。

    IMO,如果 Java 愿意,它甚至无能为力。强制关闭发生在操作系统级别,此时它会释放正在使用的内存、文件和 I/O 句柄等。Java(也没有任何其他程序)无法控制强制关闭。此时,操作系统正在负责。

    如果我错了,请有人纠正我。

    【讨论】:

    • 我不想停止程序,这不是问题。问题是当我关闭 cmd 窗口时(通过按右上角的 x),批处理文件会以不调用其关闭挂钩的方式终止我的 java 应用程序。
    • @Zoltán 请参阅上面我编辑的评论。我愿意更正。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多