19

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

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

4

3 回答 3

22

来自addShutdownHook文档:

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

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


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

引用上面的链接:

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

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

升级版。如果您可以接受本机调整,WinAPISetConsoleCtrlHandler函数会打开抑制默认行为的方法。

UPD2关于 Java 信号处理和终止的启示文章相对较旧,但编写 Java 信号处理程序部分确实可能包含您需要的内容。


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

此处包含已编译的测试二进制文件的完整源代码。

于 2012-02-14T13:33:01.687 回答
5

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

于 2014-01-31T03:40:55.320 回答
0

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

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(也没有任何其他程序)无法控制强制关闭。此时,操作系统正在负责。

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

于 2012-02-14T13:43:37.740 回答