驚きのOnClose

アプリ終了時の処理を OnClose や OnDestroy に記述していたんですが、アプリ側から終了する場合は問題ないのに、Windowsが終了する時にそれらの処理が実行されない(終了時に設定情報をiniファイルに書き出さない)という問題(本当は"問題"ではなくて、プログラマの意図と動作が一致しないだけ)が発生。
各イベントの発生状態を追跡したところ、Windows終了(ログアウトなども含む)の際は、OnCloseQueryまでしか処理されていない模様。
つまり、ユーザー指示だろうがシステム側の指示だろうが、確実に終了処理をさせたいときは OnCloseQuery に書かないといけないって事みたい。

DelphiMLを検索してみたところ、まったく同じような疑問を持った方の質問を発見。識者達との問答の末、こちらで綺麗にまとめられていたので引用しておきまー。

■ボタンを配置してCloseを実行して終了

CloseButton
FormCloseQuery
OnClose
OnHide
OnDestroy

■Alt+F4で終了
■システム・メニューから終了
■右上の×ボタンで終了

WMSysCommand
FormCloseQuery
OnClose
OnHide
OnDestroy

■タスクバーの右クリックから終了

FormCloseQuery
OnClose
OnHide
OnDestroy

■[スタート]メニューからシャットダウンで終了

WMQueryEndSession
FormCloseQuery
OnHide
OnDestroy

■ボタンを配置してApplication.Terminateを実行して終了

Applicaton.Terminate
OnHide
OnDestroy

さらにまとめると、

OnCloseQuery はアプリケーションが普通に終了する場合に発生(Terminateなどの力業の場合は該当しない)。アプリ独自のデータ保存などを行うのに向く。終了をキャンセルできるので、ユーザーにダイアログ等で保存の有無や終了キャンセルを選ばせる処理などを記述。

OnClose はアプリケーション内部からCloseメソッドを発行したり、ユーザーが×を押すなどのアクションをとった場合に発生(Terminateはもちろん、Windowsからのシャットダウン指示の場合も該当しない)。このOnCloseの存在理由は、caHide, caFree, caMinimize などを与えることで、終了アクションを他の動作に切り換えられる点(DelphiのヘルプでOnCloseを検索して参照)。ここでcaFreeが選ばれる(通常はこれがデフォルト。ただしMDIアプリの場合は異なるので注意)とフォームの破棄処理が発生、続くOnDestroyが発生する。

OnDestroy は、文字通り破棄処理が行われる時に発生。OnCreate時に生成したオブジェクトなどをここで破棄するのが適当と思われる。ここまでくるとフォームの破棄を取り消すことはできない(っぽい)。

という訳で、MDIを使わないごくフツーの単体アプリの場合は、OnCloseQueryでiniファイルやデータファイルへの書き出し、OnDestroyでOnCreateの後始末、というのがセオリーの模様。