... the behaviour you've described is by design. If that design doesn't suit your purpose, that's too bad for you. But it's not a bug. __Delete is called after the last reference to the object is released, which must always be after the variable which contained it is freed. It is not feasible to detect the "perfect order" in which objects should be released, and when circular dependencies exist, it is actually impossible. Therefore, the simplest and most predictable approach is taken: free the variables in a predetermined but undocumented order.
Any self-contained destructor will work correctly, because any references that the class has to other objects will not be released until after the destructor returns. However, circular references will prevent any of the connected objects from being freed.
If you wanted, you could prevent destruction of each class by storing its reference in an array somewhere, and then perform your cleanup of classes in whatever order you liked.
Source: [done!]variable environment release too early when ExitApp - AutoHotkey Community
Actually, I considered removing this - i.e. not calling __delete for global/static objects at all - but didn't really care enough to come to a decision. If you're defining __delete for a global/static object, you probably want it to be called.
I'm more concerned about the ExitApp function being hung up.
What are you talking about?
If __delete is being called on global/static objects, the program is already exiting. It cannot be cancelled, only postponed (such as by an infinite loop or MsgBox). If there are errors, dismiss the error dialogs and the script will exit.
Then go fix the error. If your script had an OnExit routine which showed a MsgBox, would you expect ExitApp to kick in after 3 seconds and terminate the script regardless? Even though the thread which called ExitApp is suspended? What if the destructors are performing important cleanup work that sometimes takes longer than 3 seconds?
__Delete cannot propagate exceptions up the stack, because it is invoked by the reference counting mechanism (Release), not by the script. Even if it could, it wouldn't make sense to do so. __Delete is called in all sorts of situations, including times when the stack is already unwinding due to
return,
Exit or an exception. If __Delete propagated exceptions and
try ExitApp worked, it would be the opposite of what you want. It would ignore the error and continue the script (having already destroyed some of the global/static objects), instead of finishing the destruction of global/static objects and terminating the script.
__Delete should not throw an exception. Do not suppress the error - fix your destructor to not throw it.
TASKKILL
There's no need to rely on an external executable file; you can call
DllCall("ExitProcess", "uint", ExitCode).
However, I won't be using either. It would skip a lot of other cleanup, like removing the tray icon, unregistering the keyboard/mouse hooks and non-hook hotkeys, unregistering the clipboard listener (OnClipboardChange), destroying GDI objects and GUI windows, menus, etc. At least
some of that stuff is probably beneficial.
Imagine being unable to shutdown your computer because you have 5 unsaved notepad windows.
Why imagine? Have you even
used Windows?
Perhaps ExitApp Force is more felicitous.
Force is a valid variable name for an exit code.