1

First go at starting my own service in Delphi 7. Followed the docs and made the service spawn a custom thread that beeps and logs. Only it doesn't. Last attempt was to put the same beep and log code in OnExecute event procedure, but when I start the service I get a Windows dialog saying that it was started and then stopped again.

There should be something obvious that I've overlooked in this code.

Could you have a look? I'll also accept links to simple, working, downloadable service example projects... just so I get something that is called every 10 seconds or so and I'll take it from there.

Henrik Erlandsson
  • 3,797
  • 5
  • 43
  • 63

4 Answers4

2

A bare bones service application follows.

Please note that if you want to install the service on Windows Vista and higher using ServiceApp.exe /install, you will have to ensure that you are running the app with administrator rights.

Also note that despite the fmShareDenyWrite the contents of the log file may not be viewable while the service is running. At least I couldn't open the file using Notepad++ until after I stopped the service. This may have to do with the fact that I had the service running under the system account (as opposed to my own user account).

One other remark: If you want to allow your service to be paused and continued, don't use suspend and resume. They are not thread safe and have been deprecated in D2010+. Using T(Simple)Event or something similar to control the main worker thread's execution. If you do not want to allow your service to be paused and continued, you can simply set AllowPause to False.

unit ServiceApp_fm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;

type
  TService1 = class(TService)
    procedure ServiceStart(Sender: TService; var Started: Boolean);
    procedure ServiceStop(Sender: TService; var Stopped: Boolean);
  private
    FWorker: TThread;
  public
    function GetServiceController: TServiceController; override;
  end;

var
  Service1: TService1;

implementation

{$R *.DFM}

type
  TMainWorkThread = class(TThread)
  private
    {$IFDEF UNICODE}
    FLog: TStreamWriter;
    {$ELSE}
    FLog: TFileStream;
    {$ENDIF}
    FRepetition: Cardinal;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Execute; override;
  end;

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  Service1.Controller(CtrlCode);
end;

function TService1.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;

procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
  FWorker := TMainWorkThread.Create;
  Started := True;
end;

procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  // Thread should be freed as well as terminated so we don't have a memory
  // leak. Use FreeAndNil so we can also recognize when the thread isn't
  // available. (When the service has been stopped but the process hasn't ended
  // yet or may not even end when the service is restarted instead of "just" stopped.
  if FWorker <> nil then
  begin
    FWorker.Terminate;
    while WaitForSingleObject(FWorker.Handle, WaitHint-100) = WAIT_TIMEOUT do
      ReportStatus;
    FreeAndNil(FWorker);
  end;
  Stopped := True;
end;

{ TMainWorkThread }

constructor TMainWorkThread.Create;
var
  FileName: String;
begin
  inherited Create({CreateSuspended=}False);

  FileName := ExtractFilePath(ParamStr(0)) + '\WorkerLog.txt';
  {$IFDEF UNICODE}
  FLog := TStreamWriter.Create(FileName, False, TEncoding.Unicode);
  {$ELSE}
  FLog := TFileStream.Create(FileName, fmCreate);
  {$ENDIF}
end;

destructor TMainWorkThread.Destroy;
begin
  FLog.Free;
  inherited;
end;

procedure TMainWorkThread.Execute;
var
  Text: string;
begin
  inherited;

  while not Terminated do begin
    Inc(FRepetition);
    Text := Format('Logging repetition %d'#13#10, [FRepetition]);

    {$IFDEF UNICODE}
    FLog.Write(Text);
    {$ELSE}
    FLog.Write(Text[1], Length(Text));
    {$ENDIF}
    Sleep(1000);
  end;
end;

end.
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • 1
    You cannot call the `TThread.Start()` inside of the constructor, it will raise an exception. The *correct* way to have a thread auto-resume itself after it is initialized is to simply set the `ACreateSuspended` parameter to False, nothing else. TThread will call `Resume()` (or `Start()`) on itself after the last constructor has exited. – Remy Lebeau Apr 21 '11 at 22:28
  • @Remy: thanks, I'll remove the comment about D2010. Using Resume (in D2009) does not raise an exception by the way... – Marjan Venema Apr 22 '11 at 07:29
  • Tried yours, doesn't like TEncoding. Perhaps added after Delphi 7? Be happy to try with a (simpler) replacement for 'just writing hello to to text file', which is the only reason there's any logging going on in the service. I have yet to see a character being written or anything happening in a service... :) – Henrik Erlandsson Apr 26 '11 at 07:29
  • 1
    @Henrik: Yes, TEncoding was introduced in D2009. I'll add a couple of conditional defines so you can exclude them for non-Unicode Delphi versions. – Marjan Venema Apr 26 '11 at 07:32
  • Thanks :) Just noticed the define when I refreshed, was about to say I got it working... – Henrik Erlandsson Apr 26 '11 at 08:37
  • I made some corrections to the example. Better use of TEncoding, removed the Resume() altogether, and corrected use of terminating the worker thread within the OnStop event. – Remy Lebeau Apr 26 '11 at 23:37
  • @Remy: Nice, thanks! Wasn't aware of TStreamWriter taking an Encoding in the constructor. And I agree of course with your method of thread termination. That's what I/we too have normally. In fact in addition we use a separate watchdog thread monitoring all other threads and forcing a termination of the process when one or more workers do not terminate within a given "grace" period (1-10 minutes depending on the app). – Marjan Venema Apr 27 '11 at 06:06
  • TStreamWriter (and TStreamReader) became TEncoding-aware in D2009. – Remy Lebeau Apr 28 '11 at 09:28
1

Please have a look at http://www.delphi3000.com/articles/article_3379.asp for details on creating a service. I made that post years ago, but should still work.

K.Sandell
  • 1,369
  • 10
  • 19
  • Hi Kim, yours was the first I tried now. Still no sign of life from TNTServiceThread.Execute. Tried fixing the obvious - you reset the timeout to Interval*4 on each call so that If (TimeOut=0) never becomes zero, still nothing happens, tried both windows.Beep and logging to a text file. – Henrik Erlandsson Apr 26 '11 at 07:12
  • Sorry about the dumb Timeout remark. Worked fine after linking Start/Stop/Execute/Pause/Continue/Shutdown to the Service object's Events. Zipped project is here: http://coppershade.org/file/Service.zip – Henrik Erlandsson Apr 26 '11 at 08:30
  • 1
    The problem I have with the linked example is that it has an endless (well until terminated) loop using Sleep in the main service thread (which should always be free to respond to the Service Control Manager) through an implementation of the OnExecute event. This is totally unnecessary. Also the use of Suspend and Resume to pause and continue the worker thread (the NTServiceThread instance created in the OnStart event) is inadivisable as they are not thread-safe (and thus have been deprecated in D2010+) – Marjan Venema Apr 26 '11 at 08:47
  • 1
    The post is no longer available in this URL. – nurettin Apr 16 '18 at 09:35
1

Remove below method event

procedure TAviaABSwedenAMailer.ServiceExecute(Sender: TService);
begin
  while not Terminated do
  begin
        Beep;
        Sleep(500);
        LG('Amailer is running');
                ServiceThread.ProcessRequests(False);
  end;
end;
APZ28
  • 997
  • 5
  • 4
0

The beep will not work, see this post.

Your procedure LG is not verry robust it may fail if the log file doesn't exist. Also the service user must have the right to access the file. In a first step you can run the service with your user account for testing.

Community
  • 1
  • 1
Matthias Alleweldt
  • 2,453
  • 17
  • 16