从bat脚本运行java应用程序上的Windows关闭挂钩

时间:2012-02-14 13:23:42

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

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

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

3 个答案:

答案 0 :(得分:19)

来自addShutdownHook文档:

  

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

不幸的是,我认为这里无所事事。


Windows控制台中的

CTRL-CLOSE信号。似乎不可调整。

引用上述链接:

  

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

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

<强> UPD 即可。如果您可以接受原生调整,WinAPI SetConsoleCtrlHandler函数会打开方式来抑制默认行为。

<强> UPD2 即可。 Revelations on Java signal handling and termination相对较旧的文章,但编写Java信号处理程序部分实际上可能包含您需要的内容。


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

所以,这个想法是:

  1. 保持对关闭处理程序线程的引用。
  2. 使用JNI设置自定义本机控制台处理程序例程。
  3. CTRL+CLOSE信号上调用自定义Java方法。
  4. 从该方法调用关闭处理程序。
  5. 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完成源代码。

答案 1 :(得分:4)

除了使用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的链接。

答案 2 :(得分:0)

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

taskkill是一个很好的命令,用于结束与Windows一起发布的程序(我假设你想要停止一个不同的程序,而不是批处理文件本身)。

可以按进程ID,可执行文件名,窗口标题,状态(即无响应),DLL名称或服务名称结束程序。

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

强制“program.exe”停止(通常需要使用/ f“强制”标志来强制程序停止,只需通过反复试验检查您的应用程序是否需要它):

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时,它们实际上需要很长时间才能最终终止。这是因为在OS努力释放所有资源时,一些资源(特别是某些I / O和/或文件资源)不会立即释放。

IMO,如果想要,Java甚至都无法做到。在OS级别发生强制关闭,此时它会释放正在使用的内存,文件和I / O句柄等.Java不会(也没有任何其他程序)控制强制关闭。此时,操作系统正在掌控。

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