2012-02-14 42 views
15

我有一個運行java應用程序的bat腳本。如果我按下ctrl + c,它會優雅地終止,調用所有的關閉鉤子。但是,如果我只關閉bat腳本的cmd窗口,則不會調用關閉掛鉤。從bat腳本運行的java應用程序中的Windows關閉鉤子

有沒有辦法解決這個問題?也許有辦法告訴bat腳本如何在窗口關閉時終止被調用的應用程序?

回答

18

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比較舊的文章,但是編寫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"以指示正確的關機。

編譯測試二進制文件的完整源文件here

+0

是的,我已經讀過了。我只是想知道批處理文件中是否有某種關機掛鉤。 – 2012-02-14 13:35:36

+0

我已更新答案。也許有人會提出一些控制檯的魔法。 – Mersenne 2012-02-14 13:40:14

+0

再次更新,並鏈接到Chris White的文章。 – Mersenne 2012-02-14 14:07:30

0

儘管批處理文件可能會終止,但批處理文件運行的控制檯(窗口)可能會保持打開狀態,具體取決於操作系統,命令處理器以及批處理文件執行的開始方式(從命令提示或通過快捷方式)。

taskkill是一個很好的命令來結束與Windows分發的程序(我假設你想停止一個不同的程序,而不是批處理文件本身)。

可以通過進程ID,可執行文件名稱,窗口標題,狀態(即不響應),DLL名稱或服務名稱來結束程序。

下面是根據你怎麼可能在批處理文件中使用的一些例子:

力「的Program.exe」停止(經常需要在/ F「力」標誌,以強制程序停止,只是檢查是否需要爲您的應用通過試驗和錯誤):基於其窗口標題(* wildc

taskkill /fi "Status eq NOT RESPONDING" 

停止程序:

taskkill /f /im program.exe 

停止所有無響應程序ARD這裏允許匹配任何):

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沒有(也沒有任何其他程序)能夠控制強制關閉。此時,操作系統正在負責。

有人請糾正我,如果我錯了。

+0

我不想停止一個程序,這不是問題。問題是,當我關閉cmd窗口時(通過按下x該批處理文件終止我的Java應用程序,它不會調用它的關閉鉤子。 – 2012-02-14 13:59:14

+0

@Zoltán看到我上面編輯的評論。我願意更正。 – Frankline 2012-02-14 14:14:02

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收集的。

編輯17年4月9日:更新了從github到gitlab的鏈接。

+0

您的方法不適用於我(Windows 10,Java 1.8)...該鉤子在Windows控制檯的CTRL + C中不起作用... – 2015-11-10 09:21:37

+0

只有當您強制關閉命令窗口時纔會觸發CTRL_CLOSE_EVENT CTRL + C將結束bat/cmd文件優雅而不會開火帽子事件。如果你想處理一個乾淨的關閉,使用'Runtime.addShutdownHook(Thread hook)'。 http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#addShutdownHook-java.lang.Thread- – Samah 2015-11-19 00:09:56

相關問題