Delphi Cookbook
上QQ阅读APP看书,第一时间看更新

How it works...

The project has been created using the default project template accessible by going to File | New | Other | Delphi Projects | Service Application; it has been integrated with a set of functionalities to make it real.

All the low-level interfacing with the Windows Service Manager is done by the TService class. In ServiceU.pas, 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 Windows Service Controller 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 ServiceController commands but doesn't do any actual functional work. This is done using ServiceThread.ProcessRequests(false) in a while loop.

Usually, the OnExecute event handler looks like this:

procedure TSampleService.ServiceExecute(Sender: TService); 
begin 
  while not Terminated do 
  begin 
    ServiceThread.ProcessRequests(false); 
    TThread.Sleep(1000);  
  end; 
end; 

The wait of 1,000 milliseconds is not a must, but consider that the wait time should not be too high because the service needs to be responsive to the Windows Service Controller messages. It should not be 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 is the 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 actual logic to implement the Paused state, and in this case, it is fairly simple; we 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 FPaused Boolean instance variable can be considered thread safe for this use case.

Delphi services don't have a default description under the Windows Service Manager. If we want to give them a description, we have to write a specific key in the Windows registry. Usually, this is done in the AfterInstall event. In our service, this is the code to write 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 the service is actually uninstalled.

Let's try an installation. Build the project, open the Windows command prompt (with administrator-level privileges), and go to the folder where the project has been built. Then, run this command:

C:\<ExeProjectPath>\WindowsService.exe /install  

If everything is okay, you should see this message:

Figure 1.17: The service installation is okay

Now, you can check under the Windows Services Console, and you should find the service installed. Click on Start, wait for the confirmation, and the service should start to write to its logfile.

Play with Pause and Continue and check the file activity.

Some text editors could have a problem opening the logfile while the service is writing. I suggest that you use a Unix tail clone for Windows.

There are many free choices. Here are some links: