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 testapp.cl 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 testapp.cl 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 [function] '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) description username (password "") [function] '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 http://msdn2.microsoft.com/en-us/library/ms683502.aspx 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. Example: (multiple-value-bind (success errcode) (ntservice:create-service "MyService" "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 [function] '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 testapp.cl ;;; Compiling file testapp.cl ; 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\testappa13001130442.cl ;;; 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. done. 0 cl-user(5): _______________________________________________________________________________ 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: c: 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 testapp.cl 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.