Solved

NT Services and Delphi 2

Posted on 1997-06-12
3
775 Views
Last Modified: 2010-05-18
First off, this is a 2 part question. if you answer 1 part only, ill try and award half (50) of the points.
I am using Delphi 2 BTW, and i am NOT an NT user, nor do i have access to an NT machine.. my Beta tester does... but i  need to find the following things out:1) how can i have my application start up another application as a service (the other app does support services)2) if i want to make my application be able to be loaded as a service, do i need to add any special code?Thanks.RITALIN-BOY@prodigy.net
0
Comment
Question by:Ritalin Boy
  • 2
3 Comments
 
LVL 3

Accepted Solution

by:
sperling earned 100 total points
Comment Utility
First of all, you need this unit.

---

unit Services;
// Service Control Manager functions and structures
// Feel free to use this code for whatever purpose you like,
// but be aware that it is provided "as is", and that no
// guarantees are given on correctness or usability.

// Erik Sperling Johansen, erik@info-pro.no

interface
uses
  Windows;

const
  AdvAPI32 = 'advapi32.dll';

  SERVICE_KERNEL_DRIVER          = $00000001;
  SERVICE_FILE_SYSTEM_DRIVER     = $00000002;
  SERVICE_ADAPTER                = $00000004;
  SERVICE_RECOGNIZER_DRIVER      = $00000008;

  SERVICE_DRIVER                 =
    (SERVICE_KERNEL_DRIVER or
    SERVICE_FILE_SYSTEM_DRIVER or
    SERVICE_RECOGNIZER_DRIVER);

  SERVICE_WIN32_OWN_PROCESS      = $00000010;
  SERVICE_WIN32_SHARE_PROCESS    = $00000020;
  SERVICE_WIN32                  =
    (SERVICE_WIN32_OWN_PROCESS or
    SERVICE_WIN32_SHARE_PROCESS);

  SERVICE_INTERACTIVE_PROCESS    = $00000100;

  SERVICE_TYPE_ALL               =
    (SERVICE_WIN32  or
    SERVICE_ADAPTER or
    SERVICE_DRIVER  or
    SERVICE_INTERACTIVE_PROCESS);

//
// Start Type
//

  SERVICE_BOOT_START             = $00000000;
  SERVICE_SYSTEM_START           = $00000001;
  SERVICE_AUTO_START             = $00000002;
  SERVICE_DEMAND_START           = $00000003;
  SERVICE_DISABLED               = $00000004;

//
// Error control type
//
  SERVICE_ERROR_IGNORE           = $00000000;
  SERVICE_ERROR_NORMAL           = $00000001;
  SERVICE_ERROR_SEVERE           = $00000002;
  SERVICE_ERROR_CRITICAL         = $00000003;


//
// Service State -- for Enum Requests (Bit Mask)
//
const
  SERVICE_ACTIVE                      = 1;
  SERVICE_INACTIVE                    = 2;
  SERVICE_STATE_ALL                   = SERVICE_ACTIVE + SERVICE_INACTIVE;

//
// Controls
//
const
  SERVICE_CONTROL_STOP                = 1;
  SERVICE_CONTROL_PAUSE               = 2;
  SERVICE_CONTROL_CONTINUE            = 3;
  SERVICE_CONTROL_INTERROGATE         = 4;
  SERVICE_CONTROL_SHUTDOWN            = 5;

//
// Service State -- for CurrentState
//
const
  SERVICE_STOPPED                     = 1;
  SERVICE_START_PENDING               = 2;
  SERVICE_STOP_PENDING                = 3;
  SERVICE_RUNNING                     = 4;
  SERVICE_CONTINUE_PENDING            = 5;
  SERVICE_PAUSE_PENDING               = 6;
  SERVICE_PAUSED                      = 7;

//
// Controls Accepted  (Bit Mask)
//
const
  SERVICE_ACCEPT_STOP                 = 1;
  SERVICE_ACCEPT_PAUSE_CONTINUE       = 2;
  SERVICE_ACCEPT_SHUTDOWN             = 4;

//
// Service Control Manager object specific access types
//
const
  SC_MANAGER_CONNECT                  = $0001;
  SC_MANAGER_CREATE_SERVICE           = $0002;
  SC_MANAGER_ENUMERATE_SERVICE        = $0004;
  SC_MANAGER_LOCK                     = $0008;
  SC_MANAGER_QUERY_LOCK_STATUS        = $0010;
  SC_MANAGER_MODIFY_BOOT_CONFIG       = $0020;

  SC_MANAGER_ALL_ACCESS               =
    STANDARD_RIGHTS_REQUIRED or
    SC_MANAGER_CONNECT or
    SC_MANAGER_CREATE_SERVICE or
    SC_MANAGER_ENUMERATE_SERVICE or
    SC_MANAGER_LOCK or
    SC_MANAGER_QUERY_LOCK_STATUS or
    SC_MANAGER_MODIFY_BOOT_CONFIG;

//
// Service object specific access type
//
const
  SERVICE_QUERY_CONFIG                = $0001;
  SERVICE_CHANGE_CONFIG               = $0002;
  SERVICE_QUERY_STATUS                = $0004;
  SERVICE_ENUMERATE_DEPENDENTS        = $0008;
  SERVICE_START                       = $0010;
  SERVICE_STOP                        = $0020;
  SERVICE_PAUSE_CONTINUE              = $0040;
  SERVICE_INTERROGATE                 = $0080;
  SERVICE_USER_DEFINED_CONTROL        = $0100;

  SERVICE_ALL_ACCESS                  =
    (STANDARD_RIGHTS_REQUIRED     or
    SERVICE_QUERY_CONFIG         or
    SERVICE_CHANGE_CONFIG        or
    SERVICE_QUERY_STATUS         or
    SERVICE_ENUMERATE_DEPENDENTS or
    SERVICE_START                or
    SERVICE_STOP                 or
    SERVICE_PAUSE_CONTINUE       or
    SERVICE_INTERROGATE          or
    SERVICE_USER_DEFINED_CONTROL);


//
// Handle Types
//
type
  TSCHandle = THandle;
  PSCHandle = ^TSCHandle;
  TServiceStatusHandle = INTEGER;

//
// Service Status Structure
//
  PServiceStatus = ^TServiceStatus;
  TServiceStatus =
  packed record
    dwServiceType             : INTEGER;
    dwCurrentState            : INTEGER;
    dwControlsAccepted        : INTEGER;
    dwWin32ExitCode           : INTEGER;
    dwServiceSpecificExitCode : INTEGER;
    dwCheckpoint              : INTEGER;
    dwWaitHint                : INTEGER;
  end;


//
// Service Status Enumeration Structure
//
type
  PEnumServiceStatusA = ^TEnumServiceStatusA;
  TEnumServiceStatusA =
  packed record
    lpServiceName     : PChar;
    lpDisplayName     : PChar;
    ServiceStatus     : TServiceStatus;
  end;

  TEnumServiceStatus = TEnumServiceStatusA;
  PEnumServiceStatus = ^TEnumServiceStatus;

//
// Structures for the Lock API functions
//
type
  TSCLock = POINTER;

  PQueryServiceLockStatus = ^TQueryServiceLockStatus;
  TQueryServiceLockStatus =
  packed record
    fIsLocked            : INTEGER;
    lpLockOwner          : PChar;
    dwLockDuration       : INTEGER;
  end;


//
// Query Service Configuration Structure
//
  PQueryServiceConfig = ^TQueryServiceConfig;
  TQueryServiceConfig =
  packed record
    dwServiceType      : INTEGER;
    dwStartType        : INTEGER;
    dwErrorControl     : INTEGER;
    lpBinaryPathName   : PChar;
    lpLoadOrderGroup   : PChar;
    dwTagID            : INTEGER;
    lpDependencies     : PChar;
    lpServiceStartName : PChar;
    lpDisplayName      : PChar;
  end;


//
// Function Prototype for the Service Main Function
//
type
  TServiceMainFunction = procedure (dwNumServicesArgs : INTEGER; var lpServiceArgVectors : PChar); stdcall;


//
// Service Start Table
//
type
  PServiceTableEntry = ^TServiceTableEntry;
  TServiceTableEntry =
  record
    lpServiceName    : PChar;
    lpServiceProc    : TServiceMainFunction;
  end;

//
// Prototype for the Service Control Handler Function
//
type
  THandlerFunction = procedure (dwControl : INTEGER); stdcall;


///////////////////////////////////////////////////////////////////////////
// API Function Prototypes
///////////////////////////////////////////////////////////////////////////

function ChangeServiceConfig
  (
    hService              : TSCHandle;
    dwServiceType,
    dwStartType,
    dwErrorControl        : INTEGER;
    lpBinaryPathName,
    lpLoadOrderGroup      : PChar;
    var dwTagID           : INTEGER;
    lpDependencies,
    lpServiceStartName,
    lpPassword,
    lpDisplayName         : PChar
  ) : LONGBOOL; stdcall;

function CloseServiceHandle
  (
    hSCObject             : TSCHandle
  ) : LONGBOOL; stdcall;


function ControlService
  (
    hService              : TSCHandle;
    dwControl             : INTEGER;
    VAR lpServiceStatus   : tServiceStatus
  ) : LONGBOOL; stdcall;

function CreateService
  (
    hSZManager            : TSCHandle;
    lpServiceName,
    lpDisplayName         : PChar;
    dwDesiredAccess,
    dwServiceType,
    dwStartType,
    dwErrorControl        : INTEGER;
    lpBinaryPathName,
    lpLoadOrderGroup      : PChar;
    dwTagID               : PINTEGER;
    lpDependencies,
    lpServiceStartName,
    lpPassword            : PChar
  ) : TSCHandle; stdcall;

function DeleteService
  (
    hService              : TSCHandle
  ) : LONGBOOL; stdcall;

function EnumDependentServices
  (
    hService               : TSCHandle;
    dwServiceState         : INTEGER;
    var lpServices         : TEnumServiceStatus;
    cbBufSize              : INTEGER;
    var pcbBytesNeeded     : INTEGER;
    var lpServicesReturned : INTEGER
  ) : LONGBOOL; stdcall;

function EnumServicesStatus
  (
    hSCManager             : TSCHandle;
    dwServiceType,
    dwServiceState         : INTEGER;
    lPServices             : POINTER;
    cbBufSize              : INTEGER;
    var pcbBytesNeeded     : INTEGER;
    var lpServicesReturned : INTEGER;
    var lpResumeHandle     : INTEGER
  ) : LONGBOOL; stdcall;

function GetServiceKeyName
  (
    hSCManager             : TSCHandle;
    lpDisplayName,
    lpServiceName          : PChar;
    var lpcchBuffer        : INTEGER
  ) : LONGBOOL; stdcall;

function GetServiceDisplayName
  (
    hSCManager             : TSCHandle;
    lpServiceName,
    lpDisplayName          : PChar;
    var lpcchBuffer        : INTEGER
  ) : LONGBOOL; stdcall;

function LockServiceDatabase
  (
    hSCManager             : TSCHandle
  ) : TSCLock; stdcall;

function NotifyBootConfigStatus
  (
    BootAcceptable         : LONGBOOL
  ) : LONGBOOL; stdcall;

function OpenSCManager
  (
    lpMachineName,
    lpDatabaseName         : PChar;
    dwDesiredAccess        : INTEGER
  ) : TSCHandle; stdcall;

function OpenService
  (
    hSCManager             : TSCHandle;
    lpServiceName          : PChar;
    dwDesiredAccess        : INTEGER
  ) : TSCHandle; stdcall;

function QueryServiceConfig
  (
    hService               : TSCHandle;
    lpServiceConfig        : PQueryServiceConfig;
    cbBufSize              : INTEGER;
    var pcbBytesNeeded     : INTEGER
  ) : LONGBOOL; stdcall;

function QueryServiceLockStatus
  (
    hSCManager             : TSCHandle;
    var lpLockStatus       : TQueryServiceLockStatus;
    cbBufSize              : INTEGER;
    var pcbBytesNeeded     : INTEGER
  ) : LONGBOOL; stdcall;

(*
function QueryServiceObjectSecurity
  (
    hService               : TSCHandle;
    dwSecurityInformation  : TSecurityInformation;
    var SecurityDescriptor : TSecurityDescriptor;
    cbBufSize              : INTEGER;
    var pcbBytesNeeded     : INTEGER
  ) : LONGBOOL; stdcall;
*)

function QueryServiceStatus
  (
    hService               : TSCHandle;
    var lpServiceStatus    : TServiceStatus
  ) : LONGBOOL; stdcall;

function RegisterServiceCtrlHandler
  (
    lpServiceName          : PChar;
    lpHandlerProc          : THandlerFunction
  ) : TServiceStatusHandle; stdcall;

(*
function SetServiceObjectSecurity
  (
    hService               : TSCHandle;
    dwSecurityInformation  : TSecurityInformation;
    var lpSecurityDescriptor : TSecurityDescriptor
  ) : LONGBOOL; stdcall;
*)

function SetServiceStatus
  (
    hServiceStatus         : TServiceStatusHandle;
    lpServiceStatus        : PServiceStatus
  ) : LONGBOOL; stdcall;

function StartServiceCtrlDispatcher
  (
    lpServiceStartTable    : PServiceTableEntry
  ) : LONGBOOL; stdcall;

function StartService
  (
    hService                : TSCHandle;
    dwNumServiceArgs        : INTEGER;
    lpServiceArgVectors     : PChar
  ) : LONGBOOL; stdcall;

function UnlockServiceDatabase
  (
    scLock                  : TSCLock
  ) : LONGBOOL; stdcall;

implementation

function ChangeServiceConfig;          external AdvAPI32 name 'ChangeServiceConfigA';
function CloseServiceHandle;           external AdvAPI32;
function ControlService;               external AdvAPI32;
function CreateService;                external AdvAPI32 name 'CreateServiceA';
function DeleteService;                external AdvAPI32;
function EnumDependentServices;        external AdvAPI32 name 'EnumDependentServicesA';
function EnumServicesStatus;           external AdvAPI32 name 'EnumServicesStatusA';
function GetServiceKeyName;            external AdvAPI32 name 'GetServiceKeyNameA';
function GetServiceDisplayName;        external AdvAPI32 name 'GetServiceDisplayNameA';
function LockServiceDatabase;          external AdvAPI32;
function NotifyBootConfigStatus;       external AdvAPI32;
function OpenSCManager;                external AdvAPI32 name 'OpenSCManagerA';
function OpenService;                  external AdvAPI32 name 'OpenServiceA';
function QueryServiceConfig;           external AdvAPI32 name 'QueryServiceConfigA';
function QueryServiceLockStatus;       external AdvAPI32 name 'QueryServiceLockStatusA';
function QueryServiceStatus;           external AdvAPI32;
function RegisterServiceCtrlHandler;   external AdvAPI32 name 'RegisterServiceCtrlHandlerA';
function SetServiceStatus;             external AdvAPI32;
function StartServiceCtrlDispatcher;   external AdvAPI32 name 'StartServiceCtrlDispatcherA';
function StartService;                 external AdvAPI32 name 'StartServiceA';
function UnlockServiceDatabase;        external AdvAPI32;

end.

----
1)
To start / stop any service, the service must ofcourse be registered in the SCM (Service Control Manager) database.

All of the functions below needs a handle to SCM, get this by using OpenSCManager.

Having the name shown in the "services" applet in control panel, use GetServiceKeyName to retrieve the actual service name. Use OpenService to get a handle to this service, StartService to run it and ControlService (Handle, SERVICE_CONTROL_STOP, ...) to stop it.

2)
Yes, definitely.

First of all, you should not reference the Forms.pas unit, as this will automatically create TApplication, and then you've got a lot of work trying to make Application behave properly for a service. A service should rarely be invisible, if this is necessarry then dynamically load a DLL which contains the forms and so on.

The scope from a service is started by SCM.

Main thread is created, and program entry point gets control.

Program should fill in an 0-terminated array of TServiceTableEntry records, and call StartServiceCtrlDispatcher.

StartServiceCtrlDispatcher will suspend the calling (main) thread, create a new one and call the entry point defined in the passed dispatch table in the context of this new thread.

This procedure should first of all call RegisterServiceCtrlDispatcher to register the procedure to receive control codes. Then, it should report status using SetServiceStatus with regular intervals, until it is ready to accept control codes. Then use SetServiceStatus to set SERVICE_RUNNING, and enter a loop which does whatever work needs to be done (like e.g. accepting network connections), and with regular interval check a global boolean variable which indicates whether the service has been stopped. If so, exit the loop and use SetServiceStatus to set SERVICE_STOPPED.

The procedure passed in RegisterServiceCtrlHandler will be called whenever some application uses ControlService to send a control code to you. This proc is usually just a case statement responding to SERVICE_CONTROL_XXX control codes, and possibly your own private codes. Some of the codes require that you call SetServiceStatus, some are just for informing you of what has happened. This is all documented quite well in the Win API help file. Lookup RegisterServiceCtrlHandler, and then go to the SeeAlso topic "Handler"


In short. It is not too complicated to control other services, you should be able to create such code without NT available. Writing a service, however, would be almost impossible without access to NT.

If you find these service methodics complicated, there is a shareware Delphi expert "ServiceMaker" written by a guy called Peter Antypas. I don't know whether he has finished beta-testing it, if not it'll probably not be long. I've had a glance at a beta of this product, and it looks very good. It handles all the service wrapping functionality for you, allowing you to concentrate on what your service actually should do. I'll strongly suggest using this if you need to write NT services.


Regards,

Erik.
0
 

Author Comment

by:Ritalin Boy
Comment Utility
Hey! Thanks....looks like this is EXACTLY what i needed to know...so basically , i need my app to be a console app to run as a service? anyways, thanks....I added an extra 50 points to original value....
0
 
LVL 3

Expert Comment

by:sperling
Comment Utility
Don't need to be a console app. A console app can use forms and dialogs just as an app marked as GUI. The main requirement is that you are either certain that Forms.pas is not directly or indirectly referenced in any unit, or that you modify TApplication's behaviour when it comes to e.g. showing icons/form or responding to WM_ENDSESSION messages.

Regards,

Erik
0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

Suggested Solutions

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.

728 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now