Microsoft

Creating NT services in Delphi

How can you run an application without having it run by a user who must be checked in? Run it as a service. This article shows you how to run a Delphi program as a service under NT.


An NT service application is a 32-bit Windows application that can run without requiring a user to be logged on. In fact, NT service applications seldom interact with the desktop at all. In this article, I will show you how to create, install, and configure NT services written in Delphi. You will need a basic understanding of how Delphi works but don't have to know much about services.

At your service
NT service applications can provide a lot of functionality. Look at the list when you start the Service Control Manager applet of the Control Panel. The services that run on my machine include a Web server, FTP server, firewall (all Internet related), the EventLog, Network services, and more. I even see some services that were installed with Delphi, like the Borland Socket Server and the VisiBroker Smart Agent. These last two can also run as regular applications, but it's easier to have them running automatically on my machine as a service. Services can start in two ways: manually or automatically—and they can be disabled.

The question is: When do you need to write an NT service? Some time ago, I needed a system monitor to keep an eye on the amount of free disk space on our file server. I wrote a utility to check every minute and then write that information to a log file. However, it required a user to be logged on (to start the application). And when the user logged out, my application closed. Since we didn't want a user to be logged on all night, the solution was to turn my monitoring application into an NT service. That way, it could watch the machine 24/7.

Delphi 5 service
As you would expect, implementing an NT service requires a special technique. Fortunately, Delphi 5 takes care of most of the service implementation details by introducing two special icons in the Object Repository, shown in Figure A. To open the Object Repository, choose New from the File menu.

Figure A
Object Repository window


You'll notice a Service icon and a Service Application icon. The former creates only a new Service unit; the latter creates a new Delphi project with a Service unit. Use the Service Application icon and save your project in ServProj.dpr and the unit in Server42.pas. If you take a close look at your main project file, you'll see the following code, which looks like a regular Delphi project:
 
program ServProj;
uses
  SvcMgr,
  Server42 in 'Server42.pas'{Service1: TService};
 
{$R *.RES}
 
begin
  Application.Initialize;
  Application.CreateForm(TService1, Service1);
  Application.Run;
end.
 

However, there are some differences in this code. For example, the Forms unit has been replaced by the SvcMgr unit. As a result, the Application variable is not of type TApplication but of type TServiceApplication (taking care of the NT service application details for us).

If you switch to the Server42 unit, you’ll see that it looks similar to a Delphi data module. And as with a data module, you can add just about any nonvisual component to the new service. But remember that you’re restricted to nonvisual components; you'll get an exception Controls Cannot Be Added To A Service message if you try to drop a visual component.

Configuring the service
You can use the properties and events of the new service to configure it. I will explain the most relevant and important ones.

The AllowPause and AllowStop properties speak for themselves. DisplayName specifies the name of the service in the Service Control Manager; I entered DrBob42 here. The Interactive property specifies whether the NT Service should be able to communicate with the desktop (for example, with ShowMessage). I've set Interactive to True. The ServiceStartName and Password properties specify the account that the service should be operating as. The ServiceType is set to stWin32 for a Win32 Service, and the StartType property can be stAuto, stBoot, stDisabled, stManual, or stSystem (but for a Win32 service, only Auto, Manual, and System are relevant).

That takes care of the properties. But where can you enter some code? For that, you need to switch to the Events tab of the Object Inspector. Here, you’ll see events such as Before Install and After Install and Uninstall (typically used to set up or clean up), as well as key events when interacting with the Service from the Service Control Manager, such as Start, Stop, Pause, Continue, and most importantly, the OnExecute event handler. This is the one that gets called when the Service is started and can do its job. We need to write our code skeleton here:
 
procedure TService1.ServiceExecute(Sender: TService);
begin
  // init...
 whilenot Terminated do
    ServiceThread.ProcessRequests(True);// wait for termination
  // exit...
end;
 

The while loop is needed to keep the service alive. We can do whatever we want just before and after that. So how will we do our work? For my system monitor, I had been using a Timer with a one-minute interval to monitor the amount of free disk space. Turning this into a service was easy: drop a TTimer component (from the System tab) and set the Interval property to 60,000 to make sure it fires only once every minute. But also make sure to set the Enabled property to False, so the Timer won't start right away. Now, move to the Events page of the Object Inspector and write the following code for the OnTimer event handler:
 
procedure TService1.Timer1Timer(Sender: TObject);
const
  FileName = 'c:\logfile.txt';
var
  F: TextFile;
begin
  AssignFile(f,FileName);
 if FileExists(FileName) then Append(f)
 else
    Rewrite(f);
  writeln(f,DateTimeToStr(Now),' ',DiskFree(0));
  ShowMessage(DateTimeToStr(Now));
  CloseFile(f);
end;
 

ShowMessage is called only for demonstration purposes to illustrate that the service can interact with the desktop. Now, return to the implementation of the OnExecute method of the service itself and write code to enable and disable the Timer:
 
procedure TService1.ServiceExecute(Sender: TService);
begin
  Timer1.Enabled := True;
 whilenot Terminated do
    ServiceThread.ProcessRequests(True);// wait for termination
  Timer1.Enabled := False;
end;
 

That's all folks! Now we're ready to install and test the service.

Installing the service
To install the service, you have to compile ServProj and run it with the /INSTALL command-line switch. Uninstalling requires the /UNINSTALL switch (rather than the /UNINTALL switch, as the online help mentions). Once the new NT service is installed, you can examine it in the Service Control Manager, shown in Figure B.

Figure B
Service Control Manager window


Note that the DrBob42 service is set to Automatic but hasn't been started yet. We must still explicitly start it (once). If you click on the Startup button, you can find even more information about the DrBob42 service, as Figure C shows.

Figure C
Service Information dialog box


In this dialog box, you’ll see that the service can interact with the desktop (the Interactive property is set to True), so you will be able to see ShowMessages when it's called. Close this dialog box and start the DrBob42 service, as we’ve done in Figure D.

Figure D
Start-up message


Starting the DrBob42 service triggers the OnExecute event handler, which will enable the Timer; 60 seconds later, the first message will appear (and the first lines of status information will appear in c:\logfile.txt).

Once you remove the call to ShowMessage, you can safely allow the service to continue running when no user is logged on (and no desktop is present).

Multiple services
Since we had only one service, we didn't have to worry about threading issues. However, you may want to put multiple services inside one service application, in which case you should put each service in its own thread (see the Borland Delphi 5 Developer's Guide for more details). And when you have more than one service, you should call ProcessRequest with False as argument (so it doesn't wait for the termination message and "block" the service application but allows other service threads to run, too).

Will NT services work for you?
How would you use NT services with your applications? Have you encountered problems implementing applications in this manner? Send us an e-mail with your thoughts and experiences or post a comment below.

 

Editor's Picks

Free Newsletters, In your Inbox