[AutoHotKey]OnExitが動かない条件


`Loop`を含むスクリプトで`OnExit`が動かないことがあったので調べてみた。

結論としては、ざっくり言ってスクリプトがなんらかの**待機中**だとダメなもよう。



## 基本的なOnExitの使い方

-   [OnExit \- AutoHotkey Wiki](http://ahkwiki.net/OnExit)
-   [OnExit](https://autohotkey.com/docs/commands/OnExit.htm)

サブルーチンラベルを指定する方法と関数を指定する方法があるが、どちらでも条件は変わらない。今回は、Wikiに記載のない関数指定の紹介を兼ねてそちらを例に取る。

原則として、以下を守ること。

-   `OnExit`コマンドはAuto-executeセクションに記述する
    -   サブルーチンの場合、ラベルはAuto-executeセクションに記述してはならない（その後に書く）
        -   そうしないとスクリプト起動時に実行されてしまうから
    -   関数の場合、どちらに書いてもよい
-   指定サブルーチンまたは関数内で`Exit`なり`ExitApp`を実行する
    -   関数の場合、それらコマンドの終了コードを`1`とする

以下は正常に実行される例。単純なホットキーを実装したスクリプト。

タスクトレイアイコンのコンテクストメニューからExitするとメッセージが表示される。

```ahk
/*
いちおうサブルーチンラベルの場合もコメントで併記した。
*/
; OnExit, ExitSub
OnExit("ExitFunc")

Return ; Auto-executeセクションの終了

; ExitSub:
    ; MsgBox, , , 終了したよーーーーーー！！！！！
    ; ExitApp, 1
ExitFunc(ExitReason, ExitCode)
{
    MsgBox, , , 終了したよーーーーーー！！！！！
    ExitApp, 1
}

~^S:: ; Ctrl+Sを押したときにメッセージを表示する
    MsgBox, , %A_ScriptName%, セーブしたよーーーーーー！！！！！
    Return
```



## 仕様的には問題ないと思うけどダメな例

他にもありそうだが、とりあえず検証できたのは以下の通り。



### Loopの実行中

以下は、無限ループで数値を加算しながらツールチップに表示するスクリプト。

無限ループなので、手動で終了しない限り永遠に止まらない。このような場合、`OnExit`は機能しない。

```ahk
loop
{
    counter+=1
    ToolTip, %counter%
}

/*
いちおうサブルーチンラベルの場合もコメントで併記した。
*/
; OnExit, ExitSub
OnExit("ExitFunc")

Return ; Auto-executeセクションの終了

; ExitSub:
    ; MsgBox, , , 終了したよーーーーーー！！！！！
    ; Exit, 1
ExitFunc(ExitReason, ExitCode)
{
    MsgBox, , , 終了したよーーーーーー！！！！！
    Exit, 1
}
```



### Sleep中

以下は、一定時間`Sleep`後にメッセージを表示するスクリプト。一定時間が経過して最後まで実行された場合は問題ないが、`Sleep`中にタスクトレイアイコンから終了するとダメ。

```ahk
Sleep 5000 ; 5秒待つ
MsgBox, , %A_ScriptName%, 5秒経ったよーーーーーー！！！！！

/*
いちおうサブルーチンラベルの場合もコメントで併記した。
*/
; OnExit, ExitSub
OnExit("ExitFunc")

Return ; Auto-executeセクションの終了

; ExitSub:
    ; MsgBox, , , 終了したよーーーーーー！！！！！
    ; Exit, 1
ExitFunc(ExitReason, ExitCode)
{
    MsgBox, , , 終了したよーーーーーー！！！！！
    Exit, 1
}
```



### なんちゃらWait中

以下は、メモ帳がアクティブになったらメッセージを表示するスクリプト。

一度メモ帳がアクティブになり、`WinWaitActive`が済んだらOKだが、その前にタスクトレイアイコンから終了するとダメ。

```ahk
SetTitleMatchMode, 2 ; タイトルを中間一致検索
WinWaitActive, メモ帳
MsgBox, , , メモ帳を開いたよーーーーーー！！！！！

/*
いちおうサブルーチンラベルの場合もコメントで併記した。
*/
; OnExit, ExitSub
OnExit("ExitFunc")

Return ; Auto-executeセクションの終了

; ExitSub:
    ; MsgBox, , , 終了したよーーーーーー！！！！！
    ; Exit, 1
ExitFunc(ExitReason, ExitCode)
{
    MsgBox, , , 終了したよーーーーーー！！！！！
    Exit, 1
}
```



### Msgboxの表示中

以下は、メッセージを表示するだけのスクリプト。

OKボタンをクリックして（またはEnterを押して）メッセージを消したらOKだが、メッセージが表示されたまんまタスクトレイアイコンから終了するとダメ。

```ahk
MsgBox, , , ぎゃおおおおおおおん！！！！！

/*
いちおうサブルーチンラベルの場合もコメントで併記した。
*/
; OnExit, ExitSub
OnExit("ExitFunc")

Return ; Auto-executeセクションの終了

; ExitSub:
    ; MsgBox, , , 終了したよーーーーーー！！！！！
    ; Exit, 1
ExitFunc(ExitReason, ExitCode)
{
    MsgBox, , , 終了したよーーーーーー！！！！！
    Exit, 1
}
```



## そもそも仕様的にダメそうな例

例えば、Windowsタスクマネージャからプロセスを終了するとダメ。まあそりゃそうだ。

試してないけど、コマンドプロンプトで`taskkill`とかAutoHotKeyスクリプトで`Process, close`とかもダメだろう。



## 一応の対処方法

そもそも何をしようとしてたかって、ループを手動で中断した時に指定の処理を入れたかったんだけど、`OnExit`じゃできないもよう。困った。

無理矢理感あふるるが、例えば、`Menu, Tray`で「なんかしつつExitする」メニューを作るとかすれば、似たようなことはできんでもない。

以下は、`Loop`の例にタスクトレイアイコンのコンテクストメニューの指定を加えたもの。

```ahk
Menu, Tray, Add, Exit2, ExitALT ; タスクトレイアイコンのコンテクストメニューに「Exit2」を追加

loop
{
    counter+=1
    ToolTip, %counter%
}

Return ; Auto-executeセクションの終了

ExitALT: ; 「Exit2」の処理内容。メッセージを表示してスクリプトを終了する。
    MsgBox, , , 終了したよーーーーーー！！！！！
    Exit, 1
```

タスクトレイメニューなりホットキーなりで手動で終了する場合の処理はこんな感じでできんでもないのだが、**なんらかの待機中にWindowsが再起動した場合**とかはどうしようもない。

さらなる無理やりだが、スクリプトを監視するためのスクリプトを作ってしまうくらいか。

-   実行用スクリプト
    -   終了時になんらかの処理を行う
        -   `OnExit`を使う
        -   手動終了時についでにできるようにしておく
    -   起動/終了時に監視用スクリプトを起動/終了する
-   監視用スクリプト
    -   それ自体がWindowsのログオフ/シャットダウン(再起動)に伴って終了する場合、実行用スクリプトの実行状態を確認する
        -   つまり`If (A_ExitReason = "Logoff" or "Shutdown")`
    -   実行用スクリプトが実行中でない（つまり、Windowsの終了に巻き込まれて意図せず終了している）場合、なんらかの処理を行う

とても両方書き起こす気力が沸かないが、せっかく調べたのでスクリプトの実行状態を確認する方法のみ記す。

-   [Check if script is running \- Ask for Help \- AutoHotkey Community](https://autohotkey.com/board/topic/78467-check-if-script-is-running/)

```ahk
DetectHiddenWindows, On ; 非表示ウインドウを検出する
IfWinExist, <対象スクリプトのパス> ahk_class AutoHotkey
{
    /*
    実行したい処理
    */
}
```

-   この際、対象スクリプトは`#Persistent`ないしホットキーラベル等により明示的に常駐状態である必要はない。
-   `#NoTrayIcon`でも問題ない。