Run Common Lisp programs as a service on Windows
Common Lisp
Switch branches/tags
release10.1_t6 release10.1_t5 release10.1_t4 release10.1_t3 release10.1_t2 release10.1_t1 release10.1_release_point release10.1_rc5 release10.1_rc4 release10.1_rc3 release10.1_rc2 release10.1_rc1 release10.1_beta3_release_point release10.1.beta2_release_point release10.1.beta_t6 release10.1.beta_t5 release10.1.beta_t4 release10.1.beta_t3 release10.1.beta_t2 release10.1.beta_t1 release10.1.beta_release_point release10.1.beta_rc4 release10.1.beta_rc3 release10.1.beta_rc2 release10.1.beta_rc1 release10.0_t3 release10.0_t2 release10.0_t1 release10.0_rc9 release10.0_rc8 release10.0_rc7 release10.0_rc6 release10.0_rc5 release10.0_rc4 release10.0_rc3 release10.0_rc2 release10.0_rc1 release10.0.pre-final.30_release_point release10.0.pre-final.17_release_point release10.0.beta_t13 release10.0.beta_t12 release10.0.beta_t11 release10.0.beta_t10 release10.0.beta_t9 release10.0.beta_t8 release10.0.beta_t7 release10.0.beta_t6 release10.0.beta_t5 release10.0.beta_t4 release10.0.beta_t3 release10.0.beta_t2 release10.0.beta_t1 release10.0.beta_release_point release10.0.beta_rc2 release10.0.beta_rc1 release_aclt2 release_acl100b14t8 release_acl100b11t7 release_acl100b10t6 release_acl100b8t5 release_acl100b7t4 release_acl100b6t3 release_acl100b4t2 release_acl100b2t1 release_acl90b21rc5 release_acl90b20_release_point release_acl90b20rc4 release_acl90b19rc3 release_acl90b18rc2 release_acl90b15_release_point release_acl90b15rc1 release_acl90b13t1 release_acl90b11t1 release_acl90b9t1 release_acl90b8t1 release_acl90b6_release_point release_acl90b6rc2 release_acl90b_release_point release_acl90a52rc1 release_acl90a44rc2 release_acl90a43rc1 release_acl90a39 release_acl90a39rc2 release_acl90a32 release_acl90a27 release_acl90a25 release_acl90a24 release_acl90a23 release_acl90a20 release_acl90a18 documentation_for_acl90_201200717 documentation_for_acl90_20120614 documentation_for_acl90b_20120323 documentation_for_acl90b_20120313 documentation_for_acl90b_20120214 documentation_for_acl10.1_20170309 documentation_for_acl10.1b_20160928 documentation_for_acl10.0_20150813 acl100express_Dec2015update1202_rc1 acl100express_Aug2016update081516_rc1
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Turn your Common Lisp application into a Windows NT/2000/XP/Server
2003 service with the ntservice package.

If you have a Common Lisp application that you'd like to start up
automatically when the system starts, and shut down cleanly when the
system is shutting down, then this package is for you.

Follow these steps and you'll be on the road to servicedom. 

1) First write/test/debug your application without ntservice.  Your
   application should be working properly as a standalone application
   (generated by generate-executable or generate-application) before
   attempting to involve `ntservice'.

2) Add the following form to your application, so that the :ntservice
   module is loaded and available for use:

   (eval-when (compile eval load) (require :ntservice))

3) The main function in your application should call
   ntservice:execute-service as soon as possible.
   ntservice:execute-service will be responsible for executing any
   initialization functions which your program may need.  It is also
   responsible for starting the main loop of your program.  Please
   note: ntservice:start-service calls (exit 0 :no-unwind t :quiet t)
   when the service is stopped.  If you need things to happen before
   'exit' is called, use the 'stop' keyword argument to
   execute-service to pass in a function that can do cleanup.

4) Regenerate your application w/ the updated code.

5) Call ntservice:create-service to add your program to the list of
   Windows services.  Usually this would be done by the program that
   installs your application.  See for an example of how to
   add command-line switches to your program to allow a user to
   add/remove the service easily.

6) Try it out!  Your service should now be listed in the Services
   control panel applet.  Try starting and stopping your service.  If
   it works as planned, you can use the Services control panel applet
   to make the service start automatically instead of manually.

If you want to remove your program from the list of services, you can
use ntservice:delete-service to delete the service.

The `LocalSystem' account is very powerful!  Be careful of what you
allow your program to do.  Also note that the `LocalSystem' account
usually does not have access to network filesystems.  This may lead to
confusion if your service tries to access drive letters that are
mapped to network drives, or if it tries to access remote filesystems
via UNC names (\\host\\share\file).

You can use the Services control panel applet to change who the
service runs as.  Note that no account but `LocalSystem' will be able to
interact w/ the desktop (i.e., your program's window will be invisible
if you don't run as `LocalSystem').

See for an example skeleton for a service application.  

To start and stop services programatically, you can use the
ntservice:start-service and ntservice:stop-service functions.

** function reference

execute-service service-name main &key init stop shutdown

  'service-name' is a string naming the service.  This name is the
  same name that is used when creating the service (with
  create-service).  'main' should be a function (or a symbol naming a
  function) which constitutes the main loop of your program.  This
  function will be called when the service starts running.  No
  arguments are passed to this function.  This function should never
  return [if it does, Windows will complain that the service
  terminated prematurely].

  The keyword arguments 'init', 'stop' and 'shutdown' are optional.  

  'init' specifies a function (or a symbol naming a function) that
  should be executed before the main loop is executed.  Such a
  function might load in configuration settings or verify the system
  environment.  The 'init' function should be prepared to accept a
  single argument.  This argument is the list of "Start parameters"
  that have been specified for the service.  This list is usually
  empty but can be modified using the Services control panel applet.
  If the 'init' function returns 'nil', the service will not be
  started and an error will be logged and/or reported.  Make sure
  'init' returns non-nil under normal circumstances.

  'stop' specifies a function (or a symbol naming a function) that
  should be executed when the service is to be stopped.  Such a
  function might do work that your application needs done before Lisp
  exits.  This function should do its job fairly swiftly, otherwise
  Windows might complain that the service isn't stopping properly.  No
  arguments are passed to this function.

  'shutdown' is like 'stop' and specifies a function (or a symbol
  naming a function) that should be executed when the service is to be
  stopped due to the computer being shut down.

  Please remember that ntservice:execute-service never returns to its
  caller.  It calls (exit 0 :no-unwind t :quiet t) to exit Lisp.

create-service name displaystring cmdline &key (start :manual)
					       (interact-with-desktop t)
					       (password "")

  'name' must be a string that identifies your service.  The maximum
  string length is 256 characters. The service control manager
  database preserves the case of the characters, but service name
  comparisons are always case insensitive.  Forward-slash (/) and
  back-slash (\) are invalid service name characters.

  'displaystring' must be a string that contains the display name to
  be used by user interface programs to identify the service. This
  string has a maximum length of 256 characters. The name is
  case-preserved in the service control manager. display name
  comparisons are always case-insensitive.

  'cmdline' must be a string that contains the command line for
  executing your service program.  The first word in the string must
  be the fully-qualified pathname to the executable.

  'start' can either be :manual or :auto.  If :manual, the service
  must be started and stopped manually.  If :auto, the service will
  start automatically at boot time.

  'interact-with-desktop', if true, then the service will be allowed
  to interact with the desktop.  Note that on Windows Vista,
  interaction with the desktop is limited, even with
  'interact-with-desktop' is true.  See for details.
  If 'interact-with-desktop' is true, then 'username' (see below) must be nil.

  'description', if non-nil, must be a string which explains the
  purpose of the service.  

  'username' specifies the name of the account under which the service
  should run.  If this parameter is nil or zero, the LocalSystem
  account will be used.

  'password' specifies the password for the account specified in
  'username'.  Use a null string ("") if the account has no password.

  Return values:

  If create-service is successful, it returns 't'.  If it is not
  successful, it returns two values: nil and the Windows error code.
  You can use ntserver:winstrerror to convert the code into a string.


  (multiple-value-bind (success errcode)
	  "My Common Lisp program service" 
	  "c:\\devel\\program\\program.exe /runfast /dontcrash")
     (if success
	 (format t "all is well~%")
       (error "create-service failed: ~A"
	      (ntservice:winstrerror errcode))))

  Your service will be created w/ the following properties:

    Manual start
    Run as LocalSystem account
    Allow program to interact with desktop

ntservice:set-service-description name description  

 'name' must be the name of an existing service.  'description' must
 be a string or nil.  If nil, any existing description for the named
 service will be removed.  Otherwise, the service description for the
 name service will be set.

ntservice:delete-service name				[function]

  where 'name' is the name you gave the service in your call to
  ntservice:create-service.  It seems to be possible to request deletion
  of a running service.  This disables the service from further starts
  and marks it for deletion once it stops.  delete-service turns 't' if
  the removal was successful, otherwise it returns three values: nil,
  the Windows error code, and a string with the name of the function
  that actually failed.

ntservice:start-service name &key wait			[function]

  'name' is the name of the service.  If 'wait' is true (default), then
  it the function will wait until it has confirmation that the service
  has started.  

ntservice:stop-service name &key (timeout 30)		[function]

  'name' is the name of the service.  'timeout' is the number of seconds
  to wait for the service to stop.  Currently, this function does not
  automatically stop dependent services.

** tutorial

This tutorial will show you how to use the `testapp' example.

First, startup Allegro, and do this:

cl-user(2): :cd examples/ntservice/
c:\program files\acl80\examples\ntservice\
cl-user(3): :cl
;;; Compiling file
; Fast loading c:\program files\acl80\code\ntservice.fasl
;;; Writing fasl file testapp.fasl
;;; Fasl write complete
; Fast loading c:\program files\acl80\examples\ntservice\testapp.fasl
cl-user(4): (build)
; Fast loading from bundle code\genexe.fasl.
;;; Compiling file e:\tmp\
;;; Writing fasl file e:\tmp\testappa13001130442.fasl
;;; Fasl write complete
; Fast loading c:\program files\acl80\code\genapp.fasl
;   Fast loading from bundle code\fileutil.fasl.
;   Fast loading from bundle code\build.fasl.
Bundle is up to date.
; Fast loading from bundle code\who.fasl.

This will build the test application and put the result in

  c:\Program Files\acl80\examples\ntservice\testapp\

Now, start up a cmd.exe and do this:

  cd \Program Files\acl80\examples\ntservice\testapp
  start /wait testapp /install

This will install the test service.  You can now start the test
service by doing this:

  net start MyService

The name `MyService' was set in and is just an example
service name.  You should see an Allegro CL console window with
contents something like this:
[62c] args are ("C:\\Program Files\\acl80\\examples\\ntservice\\testapp\\testapp.exe"
                "arg1" "arg2" "arg3")
[d3c] calling StartServiceCtrlDispatcher()
[a6c] service-main: calling service init func
P  Id Bix Dis Sec   dSec Pri State    Process Name, Whostate, Arrest                        
* 62c   2   2   0    0.0   0 gated    Initial Lisp Listener, waiting for service to complete
* a6c   4   1   0    0.0   0 runnable Immigrant Process for service "MyService"             
* d3c   3   1   0    0.0   0 runnable executing service                                     
[a6c] init: Start parameters: nil
[a6c] set-service-status: retrieving status
[a6c] service-main: calling service main func
[a6c] main: starting...
[d3c] service-control-handler: got INTERROGATE
[d3c] set-service-status: retrieving status

You can either close the application (with the `close' button on the
window) or you can shut down the service by doing this:

  net stop MyService

Both methods for stopping the service will work.