5

I have an application that must process some methods upon exit. However, when the user shuts down Windows without first closing my application, Windows kills the app and the shutdown methods aren't run.

How can I detect when the user has requested to shutdown or logoff of Windows? The methods I need to run take milliseconds to complete so I wouldn't need to postpone or interrupt the shutdown process, necessarily.

I already use JNA to respond to the machine being locked/unlocked, but the onMachineLogoff() method does not seem to also catch shutdown requests.

Zephyr
  • 9,885
  • 4
  • 28
  • 63
  • See if https://stackoverflow.com/questions/42598097/using-javafx-application-stop-method-over-shutdownhook helps (I don't have a Windows box available atm to test this on). – James_D Dec 15 '17 at 18:33
  • But when the use shuts down the system,the system closes all applications,and you can add your methods in `setOnCloseRequest()`. – Menai Ala Eddine - Aladdin Dec 15 '17 at 18:34
  • @XlintXms `setOnCloseRequest()` is specific to a Window/Stage, not to the entire application. – James_D Dec 15 '17 at 18:35
  • @James_D ,you are right ,because the application can has some tasks run on background without stage. – Menai Ala Eddine - Aladdin Dec 15 '17 at 18:42
  • 1
    @XlintXms Actually, I was thinking more of the case where you open multiple windows. You probably don't want to run the "exit methods" every time one of them is closed; only when the last one is closed. – James_D Dec 15 '17 at 18:43
  • Thank you, @James_D. That link helped to capture a Windows shutdown request. However, the Shutdown Hook does not appear to fire when the user just selects "Log off" in Windows. – Zephyr Dec 19 '17 at 18:18

1 Answers1

2

There are three different scenarios when you can handle shutdown and logoff events. I will focus on the Windows Application because it works for console applications also and if by some reason your application imports User32 functions then the console handle will not work.

Basically you will need 2 functions:

ATOM RegisterClassEx(WNDCLASSEX *lpwcx);

RegisterClassEx create a new kind of window with a hook(that is our shutdown/logoff handler) associated with.

HWND WINAPI CreateWindowEx(
    int dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName,
    int dwStyle, int x, int y, int nWidth, int nHeight,
    HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam
);

CreateWindowEx instantiates a new Window, this window have a event hook associated (by the registered class), this way Windows will notify the associated hook with all possible events.

Here a full working sample, just start it and logoff or shutdown the computer, after start it back, take a look at %userprofile%\shutdown-hook.log file it must have handled these events by logging something like

...
action=proc-callback, event=22 
...

code

public class Main {

    /**
     * <pre>
     * Steps:
     *
     * 1. Create a  WinProc (this function will handle all events)
     * 2. Create a window class using the created WinProc
     * 3. Create a window using the created window class
     * 4. Use the WinProc to handle shutdown events
     * </pre>
     */
    public static void main(String[] args) {
//  registering a window - https://msdn.microsoft.com/pt-br/library/windows/desktop/ms633587
//                          https://msdn.microsoft.com/pt-br/library/windows/desktop/ms633577
//  typedef struct tagWNDCLASSEX {
//    UINT      cbSize;
//    UINT      style;
//    WNDPROC   lpfnWndProc;
//    int       cbClsExtra;
//    int       cbWndExtra;
//    HINSTANCE hInstance;
//    HICON     hIcon;
//    HCURSOR   hCursor;
//    HBRUSH    hbrBackground;
//    LPCTSTR   lpszMenuName;
//    LPCTSTR   lpszClassName;
//    HICON     hIconSm;
//  } WNDCLASSEX, *PWNDCLASSEX;
//
//  ATOM WINAPI RegisterClassEx(
//    _In_ const WNDCLASSEX *lpwcx
//  );
        final WinUser.WNDCLASSEX clazz = new WinUser.WNDCLASSEX();
        clazz.lpszClassName = "MyCustomWindow";
        clazz.cbSize = Native.getNativeSize(WinUser.WNDCLASSEX.class, null);
        clazz.lpfnWndProc = new MyWinProc();

        WinDef.ATOM classInst = User32.INSTANCE.RegisterClassEx(clazz);
        System.out.printf("action=registerclass, clazz=%s, error=%d\n", classInst, Native.getLastError());

        WinDef.HWND w = User32.INSTANCE.CreateWindowEx(
            512, clazz.lpszClassName, "My Window",
            WinUser.WS_OVERLAPPEDWINDOW, -2147483648, -2147483648, 250, 100,
            null, null, null, null
        );
        System.out.printf("action=createWindow, w=%s, error=%d\n", w, Native.getLastError());

        WinUser.MSG msg = new WinUser.MSG();
        while (User32.INSTANCE.GetMessage(msg, null, 0, 0)) {
            User32.INSTANCE.DispatchMessage(msg);
        }
    }

    public interface User32 extends Library {

        User32 INSTANCE = Native.loadLibrary("User32", User32.class, W32APIOptions.UNICODE_OPTIONS);

//      ATOM WINAPI RegisterClassEx(
//          _In_ const WNDCLASSEX *lpwcx
//      );
        WinDef.ATOM RegisterClassEx(WinUser.WNDCLASSEX lpwcx);

//  HWND WINAPI CreateWindowEx(
//    _In_     DWORD     dwExStyle,
//    _In_opt_ LPCTSTR   lpClassName,
//    _In_opt_ LPCTSTR   lpWindowName,
//    _In_     DWORD     dwStyle,
//    _In_     int       x,
//    _In_     int       y,
//    _In_     int       nWidth,
//    _In_     int       nHeight,
//    _In_opt_ HWND      hWndParent,
//    _In_opt_ HMENU     hMenu,
//    _In_opt_ HINSTANCE hInstance,
//    _In_opt_ LPVOID    lpParam
//  );
        WinDef.HWND CreateWindowEx(
            int dwExStyle,
            String lpClassName,
            String lpWindowName,
            int dwStyle,
            int x,
            int y,
            int nWidth,
            int nHeight,
            WinDef.HWND hWndParent,
            WinDef.HMENU hMenu,
            WinDef.HINSTANCE hInstance,
            WinDef.LPVOID lpParam
        );

//      BOOL WINAPI GetMessage(
//          _Out_    LPMSG lpMsg,
//          _In_opt_ HWND  hWnd,
//          _In_     UINT  wMsgFilterMin,
//          _In_     UINT  wMsgFilterMax
//      );
        boolean GetMessage(WinUser.MSG lpMsg, WinDef.HWND hWnd, int wMsgFilterMin, int wMsgFilterMax);

//      LRESULT WINAPI DispatchMessage(
//          _In_ const MSG *lpmsg
//      );
        WinDef.LRESULT DispatchMessage(WinUser.MSG lpmsg);

        WinDef.LRESULT DefWindowProc(WinDef.HWND hWnd, int uMsg, WinDef.WPARAM wParam, WinDef.LPARAM lParam);
    }

    /**
     * <pre>
     * All Possible events -
     * https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx#system_defined
     * https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/um/WinUser.h
     * </pre>
     */
    public static class MyWinProc implements WinUser.WindowProc {
        private final OutputStream out;

        public MyWinProc() {
            try {
                // this is unsafe because this file will never be closed, anyway it is just for a example
                out = new FileOutputStream(new File(System.getProperty("user.home") + File.separator + "shutdown-hook.log"));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public WinDef.LRESULT callback(WinDef.HWND hWnd, int uMsg, WinDef.WPARAM wParam, WinDef.LPARAM lParam) {
            final String msg = String.format("action=proc-callback, event=%d %n", uMsg);
            System.out.print(msg);
            try {
                out.write(msg.getBytes());
                switch (uMsg){
                    case 0x0016:
                        out.write("shutdown".getBytes());
                        break;
                    case 0x0011:
                        out.write("logoff".getBytes());
                        break;
                }
                out.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return User32.INSTANCE.DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }
}

obs: Just a suggestion, depending your requirements I think that maybe can makes more sense if you just start a background thread instead and time by time do the task that you have to do because if the Windows gets the blue screen of death or the power go away or someone just plug off the power then the Windows events will not help you. Anyway background thread solution is quite more simple.

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        Executors.newSingleThreadExecutor().execute(() -> {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("do a background stuff");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {/*I will look at that in the while clause*/}
            }
        });
        System.out.println("doing another stuff");
    }
}

My dependencies

compile group: 'net.java.dev.jna', name: 'jna', version: '4.5.0'
compile group: 'net.java.dev.jna', name: 'jna-platform', version: '4.5.0'
deFreitas
  • 4,196
  • 2
  • 33
  • 43