'''AtExit''' handlers. A common problem I had was that during an application I was setting up things like pipes (in the filesystem), add tons of temporary data, etc. I was then faced with the junk the program left after ending, so I was thinking of how to solve this, and was reminded of the atexit - C function. So I wrote the following (it's quite short, that's why I include it here) # put this in a file and write the pkgIndex "magic" # package require AtExit # oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo # When you call exit, calls any proc named after the scheme # AtExit_*. It may have arbitrary arguments as long as they # are default arguments (or no args at all of course). They are # called in no special order, except if the first argument's default # value is an integer: It is taken to be a priority then. The higher, # the earlier the proc is called # ==> Should the prio arg be forced to be named 'prio*' instead ? -Martin #rename exit to something else, install an exit # fcn which is capable of calling some cleanup # routines for, well, cleaning up. rename exit __shock namespace eval ::AtExit { variable outchann namespace export outchan print proc outchan { {chan stderr} } { variable outchann if {[info exists outchann]&& ($outchann != "stderr" && $outchann != "stdout")} { close $outchann } set outchann $chan } proc noOutput { } { variable outchann if {[info exists outchann]&& ($outchann != "stderr" && $outchann != "stdout")} { flush $outchann close $outchann } catch { unset outchann } } # if you wonder what this one is for - I like, in the development # stage of a program, to print stuff of the exit handlers into the # exit-logfile (I just set ::debugging 1) - so I use # ::AtExit::print "text" in the cleanup routines for dbg msgs proc print { string } { variable outchann if {[info exists outchann]} { puts $outchann $string } } } proc exit {{status 0}} { if {[info exists ::debugging] && $::debugging} { ::AtExit::outchan [open exit.log {WRONLY CREAT TRUNC}] } foreach proc [info procs AtExit_*] { if ![llength [info args $proc]] { lappend procs1 $proc } else { set bad 0 foreach arg [set args [info args $proc]] { if ![info default $proc $arg junk] { set bad 1; break } } if {!$bad} { info default $proc [lindex $args 0] prio if [string is integer $prio] { lappend procs_prio [list $prio $proc] } } else { ::AtExit::print "Cannot call finalizer <$proc> - it needs arguments!" } } } foreach proc [lsort -integer -decreasing -index 0 $procs_prio] { ::AtExit::print "AtExit is calling finalizer <$proc>" if [catch {uplevel #0 $proc}] { ::AtExit::print "...Failure in handler!" } } foreach proc $procs1 { ::AtExit::print "AtExit is calling finalizer " if [catch {uplevel #0 $proc}] { ::AtExit::print "...Failure in handler!" } } # This is no AtExit handler because it needs to # be called as absolutely last thing. if {[info exists ::AtExit::outchann] && ($::AtExit::outchann != "stderr" && $::AtExit::outchann != "stdout")} { puts $::AtExit::outchann "Done handling exit handlers, closing stream and exitting." close $::AtExit::outchann } # bye bye __shock $status } package provide AtExit 0.1 This way you write some AtExit_* functions which expect no arguments, and which are called once you call exit. If you want to exit without calling those handlers, you can always '''shock''' (__shock) your program. Another thing which comes handy, is to install a handler for your window if you use tk: wm protocol . WM_DELETE_WINDOW exit This way even if you kill your application with the help of the windowmanager, exit will be called, and thus your exit-handlers will be called, too. I suppose this can be easily enhanced so the exit handlers have a priority; one possibility which comes to my mind is declaring the procs so that they have a default argument 'priority'; the list of procs to call would then be sorted by the values of those default parameters.. Oh, yes, it's quite easy. Suppose once I'm home and have time again I'll do just that :) ''Okay, did just that. Hope you enjoy it. -[MSW]'' ---- [MSW]: Catching the uplevel'd procs now - if you're only prepared (e.g. GUI wise) to call exit rather than __shock, you might end up with a faulty AtExit_* handler which prevents you from exitting and forces you to kill the application the hard way - which would mean your handler don't get called again... ---- [Martin Lemburg] - Sep. 10th, 2002: Inspired by the discussion about exit handlers, I wrote a package AtExit, that works in a quite different way. My AtExit package takes callbacks and priorities. The execution rule is: 1. The higher the priority, the earlier the execution 2. The later the callback is added, the earlier the execution Every callback is executed by uplevel in the scope of the procedure calling exit. Arguments are allowed, but must be satisfied by the callback definition (e.g. list tk_messageBox -message "Dummy") or by substitutions: * '''%t''' token of the exit handler * '''%p''' priority of the exit handler * '''%l''' level of the caller of exit * '''%c''' caller of exit * '''%a''' args of the caller exit An exit handler can be registered as event handler for the WM_DELETE_WINDOW event of a widget (than it is disabled as common exit handler). The former event handler is stored and will be restored, if the exit handler is unregistered again. Download via: ftp://ftp.dcade.de/pub/ml/tcl/packages/AtExit.tar.gz ftp://ftp.dcade.de/pub/ml/tcl/packages/AtExit.zip ---- [MS] contributes a very small and simple-minded atExit handler: namespace eval AtExit { variable atExitScripts [list] proc atExit script { variable atExitScripts lappend atExitScripts \ [uplevel 1 [list namespace code $script]] } namespace export atExit } rename exit AtExit::ExitOrig proc exit {{code 0}} { variable AtExit::atExitScripts set n [llength $atExitScripts] while {$n} { catch [lindex $atExitScripts [incr n -1]] } rename exit {} rename AtExit::ExitOrig exit namespace delete AtExit exit $code } namespace import AtExit::atExit The usage is really quite simple, similar to other callbacks. For example set f [open $MyTempFile ] atExit [list close $f] atExit [list file delete -force $MyTempFile] insures that the temporary file will be closed and deleted at program exit. Remark that the atExit scripts run in the namespace where atExit was called. Hence, they have access to the namespace's commands and variables. ---- [lv] there are at least 3 ways one can find oneself in an ''exitted'' state. 1. invocation of exit 2. catestrophic event - divide by zero, some other interpreter crash 3. external intervention (aka ''the hand of God'' syndrome) - such as a termination signal, processor rebooted, etc. The non-expert Tcl developer should note that AtExit takes care of the first of these. Judicial use of [catch] can attempt to take care of some of the second of these - but not all. The default Tcl has nothing to deal with the hand of God syndrome. One can handle most of these events via [tclx] if its trap capability is available on your platform. Otherwise, one has to write some custom code. ---- [EKB] 19 March 2005 When exiting in Windows, it's nice to store preferences either in the Registry or in the user's "Application Data" folder. Because I basically do Unix-style programming in a Windows environment, I prefer the second option (the closest thing to a .foo file). Here's a snippet with two routines to help do that: proc LoadPrefs {progname prefsfile} { global USERDIR USERPREFS # Get current user's home directory: If environment vars not available, # default to subfolder of the installation folder set USERDIR [file dirname $argv0] if {$tcl_platform(os) == "Windows NT"} { if {[info exists env(USERPROFILE)]} {set USERDIR $env(USERPROFILE)} } if {$tcl_platform(os) == "Windows 95"} { if {[info exists env(windir)] && [info exists env(USERNAME)]} { set USERDIR [file join $env(windir) Profiles $env(USERNAME)] } set USERDIR [file join $USERDIR "Application Data" $progname] set USERPREFS [file join $USERDIR $prefsfile] if {[file exists $USERPREFS]} { source $USERPREFS } } proc SavePrefs {} { global prefs USERDIR USERPREFS if {![file exists $USERDIR]} {file mkdir $USERDIR} # Find out if the window is zoomed if {[wm state .] == "zoomed"} { set prefs(isMaximized) true } else { set prefs(isMaximized) false } # Don't bother about errors. If can't open, then can't save prefs. That's OK. if {![catch {open $USERPREFS w} fileID]} { # Store the current window geometry puts $fileID "set prefs(geometry) [wm geometry .]" foreach item [array names prefs] { puts $fileID "set prefs($item) \"$prefs($item)\"" } close $fileID } } ---- [Category Package]