From: Anton Prokofiev on
Hello, All!
I have to write application that will work like a service under
Windows Platform.

I've found following article: http://wiki.tcl.tk/12798
but it's looks too fragmented.

So could someone provide me with a simple example of a service that
reacts on stop/start events?

Thanks in advance.




From: Patrick on
What you'll want to use is the twapi package. it has everything you
need to get going. http://twapi.magicsplat.com/services.html

Here is a script that you can start from.

I don't claim credit here, I found it on the web after going through
the same exercise you are doing now. However I have taken this base
and used it successfully to create a production grade enterprise
application wrapped into a starkit exe that is very reliable.

Once you install the example, you can telnet to port 2008 and play
echo with it.

Good luck,
Patrick



# A sample Windows service implemented in Tcl using TWAPI's windows
# services module.
#

proc usage {} {
puts stderr {
Usage:
tclsh echoservice.tcl install SERVICENAME
-- installs the service as SERVICENAME
tclsh echoservice.tcl uninstall SERVICENAME
-- uninstalls the service
Then start/stop the service using either "net start" or the services
control
manager GUI.
}
exit 1
}

package require twapi

################################################################
# The echo_server code is almost verbatim from the Tcl Developers
# Exchange samples.

set echo(server_port) 2008; # Port the echo server should listen
on
set echo(state) stopped; # State of the server

# echo_server --
# Open the server listening socket
# and enter the Tcl event loop
#
# Arguments:
# port The server's port number

proc echo_server {} {
global echo
set echo(server_socket) [socket -server echo_accept
$echo(server_port)]
}

# echo_accept --
# Accept a connection from a new client.
# This is called after a new socket connection
# has been created by Tcl.
#
# Arguments:
# sock The new socket connection to the client
# addr The client's IP address
# port The client's port number

proc echo_accept {sock addr port} {
global echo

if {$echo(server_state) ne "running"} {
close $sock
return
}

# Record the client's information

set echo(addr,$sock) [list $addr $port $sock]

# Ensure that each "puts" by the server
# results in a network transmission

fconfigure $sock -buffering line

# Set up a callback for when the client sends data

fileevent $sock readable [list echo $sock]
}

# echo --
# This procedure is called when the server
# can read data from the client
#
# Arguments:
# sock The socket connection to the client

proc echo {sock} {
global echo

# Check end of file or abnormal connection drop,
# then echo data back to the client.

if {[eof $sock] || [catch {gets $sock line}]} {
close $sock
unset -nocomplain echo(addr,$sock)
} else {
puts $sock $line
}
}

#
# Close all sockets
proc echo_close_shop {{serveralso true}} {
global echo

# Loop and close all client connections
foreach {index conn} [array get echo addr,*] {
close [lindex $conn 2]; # 3rd element is socket handle
unset -nocomplain echo($index)
}

if {$serveralso} {
close $echo(server_socket)
unset -nocomplain echo(server_socket)
}
}

#
# A client of the echo service.
#

proc echo_client {host port} {
set s [socket $host $port]
fconfigure $s -buffering line
return $s
}

# A sample client session looks like this
# set s [echo_client localhost 2540]
# puts $s "Hello!"
# gets $s line


################################################################
# The actual service related code

#
# Update the SCM with our state
proc report_state {name seq} {
if {[catch {
set ret [twapi::update_service_status $name $seq
$::echo(server_state)]
} msg]} {
::twapi::eventlog_log "Service $name failed to update status: $msg"
}
}

# Callback handler
proc service_control_handler {control {name ""} {seq 0} args} {
global echo
switch -exact -- $control {
start {
if {[catch {
# Start the echo server
echo_server
set echo(server_state) running
} msg]} {
twapi::eventlog_log "Could not start echo server: $msg"
}
report_state $name $seq
}
stop {
echo_close_shop
set echo(server_state) stopped
report_state $name $seq
}
pause {
# Close all client connections but leave server socket open
echo_close_shop false
set echo(server_state) paused
report_state $name $seq
}
continue {
set echo(server_state) running
report_state $name $seq
}
userdefined {
# Note we do not need to call update_service_status
set ::done 1; # Hard exit
}
all_stopped {
# Mark we are all done so we can exit at global level
set ::done 1
}
default {
# Ignore
}
}
}


################################################################
# Main code

# Parse arguments
if {[llength $argv] != 2} {
usage
}

set service_name [lindex $argv 1]
switch -exact -- [lindex $argv 0] {
service {
# We are running as a service
if {[catch {
twapi::run_as_service [list [list
$service_name ::service_control_handler]]
} msg]} {
twapi::eventlog_log "Service error: $msg"
}
# We sit in the event loop until service control stop us through
# the event handler
vwait ::done
}
install {
if {[twapi::service_exists $service_name]} {
puts stderr "Service $service_name already exists"
exit 1
}

# Make the names a short name to not have to deal with
# quoting of spaces in the path

set exe [file nativename [file attributes [info nameofexecutable] -
shortname]]
set script [file nativename [file attributes [file normalize [info
script]] -shortname]]
twapi::create_service $service_name "$exe $script service
$service_name"
}
uninstall {
if {[twapi::service_exists $service_name]} {
twapi::delete_service $service_name
}
}
default {
usage
}
}

exit 0



From: Patrick on
Also, if you are looking for a nice way to install it as a service,
take a look at Installjammer, a tcl/tk based install package that
rivals commercial installers.