hennesse has asked for the wisdom of the Perl Monks concerning the following question:

Should this be a Windows Service ?

I have a large, multi-threaded daemon program which I’m porting from FreeBSD to Windows, and I’m not sure what the correct approach is.

The daemon interfaces with some industrial process control hardware, and the user interfaces with the daemon through a web browser. In Unix, the user just types Start or Stop on the command line to control the daemon.

For the Windows implementation, I want to have a System Tray icon which, when clicked, brings up a little GUI window that has “Start” and “Stop” buttons, and a little more. Whenever the user turns on his computer, the System Tray icon should be there, and the daemon should not be running.

Having never done such a thing before (I’m an Unix guy), my first thought was to bundle both the System Tray icon (Win32::GUI) and the main daemon starter/stopper into a Service (code below). This works beautifully from the point of view of starting and stopping the daemon from the little window, but I suspect it’s a really, really bad Windows service.

My next thought was that maybe I don’t really need a Service after all – if I can get the icon to somehow (?) always appear in the System Tray, the icon program can start the daemon by adding new threads, and stop it by telling those threads to exit.

Or maybe the System Tray Icon should be one program, and the daemon be a service.

Whoosh! I’m confused – what’s the right approach to this problem?

Thanks – Dave

#!/usr/bin/perl # ---------------------------------------------------------------- # starter.pl # ---------------------------------------------------------------- # # Windows environment only # # Registers as a Windows service # Displays an icon in the System Tray # The icon opens a GUI window with Start/Stop buttons # Start opens supervisor and the 4 dwarves in new threads # Stop sends a "goodbye" message to supervisor, # who then relays the goodbye to the dwarves # # ---------- ---- ---------------------------------------------- # DATE VERS DESCRIPTION # ---------- ---- ---------------------------------------------- # 2011-10-21 1.00 First Version # ---------- ---- ---------------------------------------------- # ---------------------------------------------------------------- # Directives # ---------------------------------------------------------------- use threads; use threads::shared; use thread::Queue; use strict; use Socket; use Fcntl; use Carp; use Errno; use Time::HiRes qw ( time alarm sleep ); use Brewery; use PID; use SSR; use Win32::Daemon; use Win32::GUI(); # ---------------------------------------------------------------- # Variables # ---------------------------------------------------------------- my $thr1; my $thr2; my $thr3; my $thr4; my $thr5; my %Context; # ---------------------------------------------------------------- # Main Program # ---------------------------------------------------------------- Win32::Daemon::RegisterCallbacks( { start => \&Callback_Start, running => \&Callback_Running, stop => \&Callback_Stop, pause => \&Callback_Pause, continue => \&Callback_Continue, } ); %Context = ( last_state => SERVICE_STOPPED, start_time => time(), ); # Start the service passing in a context and # indicating to callback using the "Running" event # every 2000 milliseconds (2 seconds). Win32::Daemon::StartService( \%Context, 2000 ); sub Callback_Running { my( $Event, $Context ) = @_; # Note that here you want to check that the state # is indeed SERVICE_RUNNING. Even though the Running # callback is called it could have done so before # calling the "Start" callback. if( SERVICE_RUNNING == Win32::Daemon::State() ) { chdir("c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads +"); require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threa +ds/supervisor.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threa +ds/ctrlman.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threa +ds/pidman.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threa +ds/progman.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threa +ds/webman.pl"; my $splashimage= new Win32::GUI::Bitmap("c:/Documents and Settings +/Owner/Desktop/WWW/brewery/threads/teb.bmp"); my $main = Win32::GUI::Window->new( -name => 'Main', -width => 220, -height => 100, -text => 'The Electronic Brewery', -background => '#c0c0c0' ); my $theImage = $main->AddLabel( -bitmap => $splashimage, -pos => [0,0] ); my $theLabel = $main->AddLabel( -text => 'The Brewery is: STOPPED', -pos => [40,25] ); my $start_button = $main->AddButton( -name => 'Start', -text => 'Start', -pos => [ 60, 50 ] ); my $stop_button = $main->AddButton( -name => 'Stop', -text => 'Stop', -visible => 0, -pos => [ 100, 50 ] ); my $icon = new Win32::GUI::Icon('c:/Documents and Settings/Owner/D +esktop/WWW/brewery/favicon.ico'); my $ni = $main->AddNotifyIcon( -name => "NI", -icon => $icon, -tip => "The Electronic Brewery" ); Win32::GUI::Dialog(); sub Main_Terminate { $main->Disable(); $main->Hide(); return 0; } sub Main_Minimize { $main->Disable(); $main->Hide(); return 1; } sub NI_Click { $main->Enable(); $main->Show(); return 1; } sub Start_Click { $theLabel->Text('The Brewery is RUNNING'); $start_button->Hide(); $stop_button->Show(); $thr1 = threads->create(\&supervisor); $thr2 = threads->create(\&ctrlman); $thr3 = threads->create(\&pidman); $thr4 = threads->create(\&progman); $thr5 = threads->create(\&webman); return 0; } sub Stop_Click { $theLabel->Text('The Brewery is STOPPED'); $start_button->Show(); $stop_button->Hide(); my $iaddr = inet_aton("127.0.0.1"); my $proto = getprotobyname("udp"); my $port = 0; my $paddr = sockaddr_in( $port, $iaddr ); socket( SUPERVISOR, PF_INET, SOCK_DGRAM, $proto ) || die "sock +et: $!"; bind( SUPERVISOR, $paddr ) || die "bind: $!"; $port = 5000; my $dest = sockaddr_in($port, $iaddr); send(SUPERVISOR, "goodbye", 0, $dest); return 0; } } } sub Callback_Start { my( $Event, $Context ) = @_; # Initialization code # ...do whatever you need to do to start... $Context->{last_state} = SERVICE_RUNNING; Win32::Daemon::State( SERVICE_RUNNING ); } sub Callback_Pause { my( $Event, $Context ) = @_; $Context->{last_state} = SERVICE_PAUSED; Win32::Daemon::State( SERVICE_PAUSED ); } sub Callback_Continue { my( $Event, $Context ) = @_; $Context->{last_state} = SERVICE_RUNNING; Win32::Daemon::State( SERVICE_RUNNING ); } sub Callback_Stop { my( $Event, $Context ) = @_; $Context->{last_state} = SERVICE_STOPPED; Win32::Daemon::State( SERVICE_STOPPED ); # We need to notify the Daemon that we want to stop callbacks +and the service. Win32::Daemon::StopService(); }

Replies are listed 'Best First'.
Re: Should this be a Windows Service ?
by BrowserUk (Patriarch) on Oct 21, 2011 at 19:35 UTC
    Should this be a Windows Service ? ... Or maybe the System Tray Icon should be one program, and the daemon be a service.

    The primary criteria of whether a program should be a service or not is: Should the program continue to run when no one is logged in?

    And that boils down to one of two possibilities: a) Does it need to be running 24/7 (or, at least whenever the system is powered on) in order to fulfill its function? or b) Does it continue to perform some useful function when there is no one logged on?

    (Note: Waiting dormant for someone to log on so they do not have to type 'start xxx' does not qualify!)

    Also note that it perfectly possible to start and stop services from the command line on windows. See net help start and net help stop.

    From what you've written, it sounds like it could justify being a service, but that it does not really benefit from the addition of the taskbar icon.

    If you can justify -- because of some or all of your target audiences expectations -- to have a gui way of starting and stopping the service (in addition to the browser interface), I'd suggest that you write that as a small, standalone application (that perhaps installs itself to the taskbar when minimised if it is really going to be used frequently enough to justify that occupation of system resources). This would be completely separate from the service itself and would be (initially) started from a normal start bar icon, (or from the command line) by the user when they deem it necessary.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Should this be a Windows Service ?
by hennesse (Beadle) on Oct 21, 2011 at 21:10 UTC

    The operator turns on the process hardware, starts the daemon, opens a web browser, works for 8 hours, then turns everything off. Normally, the platform would be a dedicated single-user PC, but out in the real world, it could be run on a laptop or a multi-user PC. So, to make it resilient against an operator inadvertently logging out while the process runs unattended for an hour, I think the daemon should continue to run with no one logged in. So I think I need a service.

    The browser (either the local one or another one on a LAN) can not start or stop the daemon.

    The system requires Apache to be running, and Apache has a system tray starter/stopper. Part of the startup procedure would be to click the Apache icon and ensure that Apache is running. Thus my idea for a system tray icon for starting and stopping my daemon – the operator goes to the same place to perform both functions – and my icon should act similar to Apache’s. So I’d like to keep the icon.

    OK, so two distinct programs. Got some questions.

    Service – the service is supposed to be able to pause and continue, but that makes no sense in my application. How do I tell Win32::Daemon “can’t pause”?

    Icon – (1) the Apache icon appears whenever a user logs on, and it can also be added/removed from the Start Menu. How do I get mine to appear whenever a user logs on? (2) The Apache icon knows the status of the Apache service. How does it do that?

    Dave

Re: Should this be a Windows Service ?
by locked_user sundialsvc4 (Abbot) on Oct 22, 2011 at 13:33 UTC

    Yes, a service, without a doubt...