How do one write a service for Win32 ?

Notify me of responses

I have written a caller-id server wich reads a string from a modem
and posts it to a sql database.. Simple routine.. Now i am bored of
starting it manually everytime i restart my win2k server.. On my
linux box i allready run it as a deamon.. But since my friends would
like to have it in win32 env. I am wondering if someone has a
snippet how to build a win32 service and how i shall do to make it
pop up amongst the services..

Thanx in advance
Carl Saxmark

-- Carl Saxmark, June 26, 2004 09:58 PM (email)

Answers:


I did it. It works fine. To allow easy debuging I put whole functionality in unit with following interface:

FUNCTION MyServiceInitialization ( argc : Cardinal ; argv : ppChar ; VAR specificError : Cardinal )

FUNCTION MyServiceStart ( VAR specificError : Cardinal ) : Cardinal ;

PROCEDURE MyServiceStop ;

PROCEDURE MyServicePause ;

PROCEDURE MyServiceContinue ;

Names are self explanatory (I hope). To test this unit I run it from normal application program. But to install it as a service I have prepared special main program interfacing to Windows service machine. You can find source at the bottom of message. Ignore calls to TraceInit() and Trace(), Replace oll occurances of NetPeruse to your service name and go!
This is based on information found in MSDN.

Adam Kaminski

-----------------------------------------------------------------

PROGRAM NetPeruseService;

USES
  Windows, NetPeruseUnit, TraceUnit;

CONST
  SCMDLL = 'ADVAPI32.DLL';

TYPE
  SERVICE_STATUS_HANDLE  = Cardinal;
  LPSERVICE_MAIN_FUNCTION = PROCEDURE ( argc : Cardinal ; VAR argv : ppChar );
  LPHANDLER_FUNCTION      = PROCEDURE ( Opcode : Cardinal );
  LPSERVICE_TABLE_ENTRY  = ^SERVICE_TABLE_ENTRY;
  SERVICE_TABLE_ENTRY    = RECORD
                              lpServiceName : pChar;
                              lpServiceProc : LPSERVICE_MAIN_FUNCTION;
                            END;

CONST
  SERVICE_WIN32_OWN_PROCESS      = $00000010;
  //SERVICE_WIN32_SHARE_PROCESS    = $00000020;
  //SERVICE_WIN32                  = (SERVICE_WIN32_OWN_PROCESS OR SERVICE_WIN32_SHARE_PROCESS);
//
// Controls
//
  SERVICE_CONTROL_STOP          = $00000001;
  SERVICE_CONTROL_PAUSE          = $00000002;
  SERVICE_CONTROL_CONTINUE      = $00000003;
  SERVICE_CONTROL_INTERROGATE    = $00000004;
  SERVICE_CONTROL_SHUTDOWN      = $00000005;
//
// Service State -- for CurrentState
//
  SERVICE_STOPPED                = $00000001;
  SERVICE_START_PENDING          = $00000002;
  SERVICE_STOP_PENDING          = $00000003;
  SERVICE_RUNNING                = $00000004;
  //SERVICE_CONTINUE_PENDING      = $00000005;
  //SERVICE_PAUSE_PENDING          = $00000006;
  SERVICE_PAUSED                = $00000007;
//
// Controls Accepted  (Bit Mask)
//
  SERVICE_ACCEPT_STOP            = $00000001;
  SERVICE_ACCEPT_PAUSE_CONTINUE  = $00000002;
  SERVICE_ACCEPT_SHUTDOWN        = $00000004;

TYPE
  //LPSERVICE_STATUS        = ^SERVICE_STATUS;
  SERVICE_STATUS          = RECORD
                              dwServiceType            : Cardinal;
                              dwCurrentState            : Cardinal;
                              dwControlsAccepted        : Cardinal;
                              dwWin32ExitCode          : Cardinal;
                              dwServiceSpecificExitCode : Cardinal;
                              dwCheckPoint              : Cardinal;
                              dwWaitHint                : Cardinal;
                            END;

FUNCTION StartServiceCtrlDispatcher ( lpServiceStartTable : LPSERVICE_TABLE_ENTRY ) : Boolean ;
  stdcall ; external SCMDLL name 'StartServiceCtrlDispatcherA';

FUNCTION RegisterServiceCtrlHandler ( lpServiceName : pChar ; lpHandleProc : LPHANDLER_FUNCTION ) : SERVICE_STATUS_HANDLE ;
  stdcall ; external SCMDLL name 'RegisterServiceCtrlHandlerA';

FUNCTION SetServiceStatus ( hServiceStatus : SERVICE_STATUS_HANDLE ; VAR ServiceStatus : SERVICE_STATUS ) : Boolean ;
  stdcall ; external SCMDLL name 'SetServiceStatus';

VAR
  MyServiceStatus      : SERVICE_STATUS;
  MyServiceStatusHandle : SERVICE_STATUS_HANDLE;


PROCEDURE MyServiceCtrlHandler ( Opcode : Cardinal ) ; stdcall ;

BEGIN { MyServiceCtrlHandler }
Trace(Trace1, Start, 'MyServiceCtrlHandler');
CASE ( Opcode ) OF
  SERVICE_CONTROL_PAUSE:
        BEGIN
        // Do whatever it takes to pause here.
        MyServicePause();
        MyServiceStatus.dwCurrentState := SERVICE_PAUSED;
        END;
  SERVICE_CONTROL_CONTINUE:
        BEGIN
        // Do whatever it takes to continue here.
        MyServiceContinue();
        MyServiceStatus.dwCurrentState := SERVICE_RUNNING;
        END;
  SERVICE_CONTROL_SHUTDOWN, // Do whatever it takes to shutdown here.
  SERVICE_CONTROL_STOP:    // Do whatever it takes to stop here.
        BEGIN
        IF ( Opcode = SERVICE_CONTROL_SHUTDOWN )
          THEN Trace(TraceLog, Cont, 'Shutdown request received')
          ELSE Trace(TraceLog, Cont, 'Stop request received');
        MyServiceStop;
        MyServiceStatus.dwCurrentState := SERVICE_STOP_PENDING; { W przykladzie bylo SERVICE_STOPPED_PENDING }
        MyServiceStatus.dwWin32ExitCode := 0;
        MyServiceStatus.dwCheckPoint  := 0;
        MyServiceStatus.dwWaitHint    := 0;
        IF ( NOT SetServiceStatus( MyServiceStatusHandle, MyServiceStatus) ) THEN
          BEGIN
          Trace(TraceError, Cont, 'SetServiceStatus: ', GetLastError());
          Exit;
          END;
        { leaving NetPeruse }
        Trace(Trace1, Finish, 'MyServiceCtrlHandler');
        Exit;
        END;
  SERVICE_CONTROL_INTERROGATE:
        BEGIN
        // Do whatever it takes to pause here.
        //MyServiceStatus.dwCurrentState := SERVICE_PAUSED;
        END;
END; { CASE ( Opcode ) OF }
{ send current status }
IF ( NOT SetServiceStatus( MyServiceStatusHandle, MyServiceStatus) )
  THEN Trace(TraceError, Cont, 'SetServiceStatus: ', GetLastError());
Trace(Trace1, Finish, 'MyServiceCtrlHandler');
END;  { MyServiceCtrlHandler }

PROCEDURE MyServiceMain ( argc : Cardinal ; VAR argv : ppChar ) ; stdcall ;

VAR
  status        : Cardinal;
  specificError : Cardinal;

BEGIN { MyServiceMain }
Trace(Trace1, Start, 'MyServiceMain');
MyServiceStatus.dwServiceType            := SERVICE_WIN32_OWN_PROCESS;
MyServiceStatus.dwCurrentState            := SERVICE_START_PENDING;
MyServiceStatus.dwControlsAccepted        := SERVICE_ACCEPT_STOP OR SERVICE_ACCEPT_PAUSE_CONTINUE OR SERVICE_ACCEPT_SHUTDOWN;
MyServiceStatus.dwWin32ExitCode          := 0;
MyServiceStatus.dwServiceSpecificExitCode := 0;
MyServiceStatus.dwCheckPoint              := 0;
MyServiceStatus.dwWaitHint                := 0;

MyServiceStatusHandle := RegisterServiceCtrlHandler('NetPeruse', MyServiceCtrlHandler);
IF ( MyServiceStatusHandle = 0 ) THEN
  BEGIN
  Trace(TraceError, Cont, 'RegisterServiceCtrlHandler failed: ', GetLastError());
  Exit;
  END;

{ Inicjalizacja service'u }
status := MyServiceInitialization(argc, argv, specificError);
IF ( status <> 0 ) THEN
  BEGIN
  MyServiceStatus.dwCurrentState            := SERVICE_STOPPED;
  MyServiceStatus.dwWin32ExitCode          := status;
  MyServiceStatus.dwServiceSpecificExitCode := specificError;
  MyServiceStatus.dwCheckPoint              := 0;
  MyServiceStatus.dwWaitHint                := 0;
  SetServiceStatus( MyServiceStatusHandle, MyServiceStatus);
  Trace(TraceError, Cont, 'MyServiceInitialization: ', specificError);
  Exit;
  END;

{ Initialization complete - report running status }
MyServiceStatus.dwCurrentState            := SERVICE_RUNNING;
MyServiceStatus.dwCheckPoint              := 0;
MyServiceStatus.dwWaitHint                := 0;
IF ( NOT SetServiceStatus( MyServiceStatusHandle, MyServiceStatus) ) THEN
  BEGIN
  Trace(TraceError, Cont, 'SetServiceStatus: ', GetLastError());
  Exit;
  END;

status := MyServiceStart(specificError);

MyServiceStatus.dwCurrentState            := SERVICE_STOPPED;
MyServiceStatus.dwWin32ExitCode          := status;
MyServiceStatus.dwServiceSpecificExitCode := specificError;
MyServiceStatus.dwCheckPoint              := 0;
MyServiceStatus.dwWaitHint                := 0;
SetServiceStatus( MyServiceStatusHandle, MyServiceStatus);

Trace(Trace1, Finish, 'MyServiceMain');

END;  { MyServiceMain }

VAR
  DispatchTable : ARRAY [ 0 .. 1 ] OF SERVICE_TABLE_ENTRY;
  specificError : Cardinal;


BEGIN { NetPeruseService }
TraceInit(ParamStr(1)+'NetPeruse.log');
Trace(TraceLog, Start, 'NetPeruseService');
IF ( MyServiceInitialization ( argc, argv, specificError ) <> 0 ) THEN
  BEGIN
  Trace(TraceError, Cont, 'MyServiceInitialization: ', specificError);
  Exit;
  END;
DispatchTable[0].lpServiceName := 'NetPeruse';
DispatchTable[0].lpServiceProc := @MyServiceMain;
DispatchTable[1].lpServiceName := NIL;
DispatchTable[1].lpServiceProc := NIL;
IF ( NOT StartServiceCtrlDispatcher(DispatchTable) ) THEN
  BEGIN
  Trace(TraceError, Cont, 'StartServiceCtrlDispatcher: ', GetLastError());
  Trace(Trace1, Finish, 'NetPeruseService');
  Exit;
  END;
Trace(TraceLog, Finish, 'NetPeruseService');
END. { NetPeruseService }

-- Adam Kaminski, November 26, 2004 07:06 PM (email)