Creating a Windows service
Some kind of application needs to be running H24. Usually, these are network servers or data transfer / monitoring applications. In these cases, you probably start with a normal GUI or console application; however, when the systems are to be used in production, you face a lot of problems related to the Windows session termination, reboots, user rights, and other issues related to the server environment.
Getting ready
The way to go, in the previous scenario, is to develop a Windows service. In this recipe, we'll see how to write a good Windows service scaffold and this can be the skeleton for many other services, so feel free to use this code as a template to create all services that you will need.
How to do it…
The project has been created starting from the default project template accessible from File | New | Other | Delphi Projects | Service Application and then has been integrated with a set of functionalities to make it real.
All the low-level interfacing with Windows Service Manager is done by the TService
class. In the ServiceU.pas
component, there is the actual descendant of TService
that represents the Windows service we are implementing. Its event handlers are used to communicate with the operating system.
Usually, a service needs to respond to the Windows ServiceController
commands independently of what it is doing, so we need a background thread to do the actual work, while the TService.OnExecute
event should not do any real work (this is not a must, but usually is the way to go). The unit named WorkerThreadU.pas
contains the thread and the main service needed to hold a reference to the instance of this thread.
The background thread starts when the service is started (the OnStart
event) and stops when the service is stopped (the OnStop
event). The OnExecute
event waits and handles the ServiceController
commands but doesn't do any actual functional work. This is done using the ServiceThread.ProcessRequests(false);
event in a while
loop.
Usually, the OnExecute
event handler looks like the following:
procedure TSampleService.ServiceExecute(Sender: TService); begin while not Terminated do begin ServiceThread.ProcessRequests(false); TThread.Sleep(1000); end; end;
The waiting time of 1000
milliseconds is not a must, but consider that the wait time should be not too high because the service needs to be responsive to the Windows service controller messages, and not too low because, otherwise, the thread context switch may waste resources.
The background thread writes a line in a logfile once a second. While it is in a Paused
state, the service stops writing. When the service continues, the thread will restart writing the log line. In the service event handlers, there is a logic to implement this change of state:
procedure TSampleService.ServiceContinue(Sender: TService; var Continued: Boolean); begin FWorkerThread.Continue; Continued := True; end; procedure TSampleService.ServicePause(Sender: TService; var Paused: Boolean); begin FWorkerThread.Pause; Paused := True; end;
In the thread, there is the actual logic to implement the Paused
state and in this case, it is fairly simple; we've to pause the writing of the logfile.
Here's an extract:
Log := TStreamWriter.Create( TFileStream.Create(LogFileName, fmCreate or fmShareDenyWrite)); try while not Terminated do begin if not FPaused then begin Log.WriteLine('Message from thread: ' + TimeToStr(now)); end; TThread.Sleep(1000); end; finally Log.Free; end;
The boolean
instance variable FPaused
can be considered as a thread safe for this use.
Delphi services don't have a default description under Windows Service Manager. If we want to give a description, we have to write a specific key in the Windows registry. Usually, this is done in the AfterInstall
event. In our service, write the following code in the AfterInstall
event handler:
procedure TSampleService.ServiceAfterInstall( Sender: TService); var Reg: TRegistry; //declared in System.Win.Registry; begin Reg := TRegistry.Create(KEY_READ or KEY_WRITE); try Reg.RootKey := HKEY_LOCAL_MACHINE; if Reg.OpenKey( '\SYSTEM\CurrentControlSet\Services\' + name, False {do not create if not exists}) then begin Reg.WriteString('Description', 'My Fantastic Windows Service'); Reg.CloseKey; end; finally Reg.Free; end; end;
It is not necessary to delete this key in the AfterUnInstall
event because Windows deletes all the keys related to the service (under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<MyServiceName>
) when it is actually uninstalled.
Let's try an installation. Build the project, open the Windows command prompt, and go to the folder where the project has been built and run this command:
C:\<ExeProjectPath>\WindowsService.exe /install
If all is okay, you should see this message:
Now, you can check in the Windows Services Console. You should find the service installed. Click on Start, wait for the confirmation, and the service should start to write its logfile.
Play with Pause, Continue, and check the file activity.
Note
Some text editors could have problem with opening the logfile while the service is writing. I suggest using a Unix tail clone for Windows.
There are many free choices. Here are some links:
There's more...
Windows services are very powerful. Using the abstractions that Delphi provides, you can also create an application that can act as a normal GUI application or as a Windows service after reading a parameter on the command line.
In the recipe folder, there is another recipe called 20_WindowsServiceOrGUI
.
This application can be used as a normal Windows service using the normal command-line switches used so far, but if launched with /GUI
, it acts as a GUI application and can use the same application code (not TService
). In our example, the GUI version uses the same worker thread as the service version. This can be very useful for debugging purposes.
Run the application with the following command:
C:\<ExeProjectPath>\WindowsServiceOrGUI.exe /GUI
You will get a GUI version of the service, as shown in the following screenshot:
Using the TService.LogMessage method
If something happens during the execution of your service which you want to log and you want to log into the system logger, you can use the LogMessage
method to save a message, which can be viewed later using Windows built-in event viewer.
You can call the LogMessage
method using appropriate logging type:
LogMessage('Your message goes here for SUCCESS', EVENTLOG_SUCCESS, 0, 1);
If you check the event in Event Viewer, you will find a lot of garbage text that complains about the lack of description for the event.
If you really want to use Event Viewer to view your log messages (when I can, I use a text logfile and don't care about Event Viewer, but there are scenarios where Event Viewer log is needed), you have to use the Microsoft Message Compiler.
The Microsoft Message Compiler is a tool able to compile a file of messages in a set of RC files. Then those files must be compiled by a resource compiler and linked to your executable.
More information about message compiler and steps needed to provide the needed description for the log event can be found at the following link:
http://www.codeproject.com/Articles/4166/Using-MC-exe-message-resources-and-the-NT-event-lo