I'm using Delphi 11 and I'm trying to learn about Windows services.
I've created a very basic minimalistic service that should basically just start and sleep in a loop until stopped. I got most of the code directly from Embarcadero's blog "Developing Windows Services in Windows 11: Best Practices and Tools". But when I install the service with this Powershell command with Administrator access:
sc create TestSvc binPath= "C:\Actual\Path\TestSvc.exe" start= demand
and then try to run it via sc start TestSvc
the outcome is:
SERVICE_NAME: TestSvc TYPE : 10 WIN32_OWN_PROCESS STATE : 2 START_PENDING (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x7d0 PID : 7292 FLAGS :
And it stays forever stuck in the START_PENDING
state, while the Task Manager keeps showing it as:
Starting
My TService1
class has only these few methods:
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service1.Controller(CtrlCode);
end;
function TService1.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TService1.ServiceContinue(Sender: TService; var Continued: Boolean);
begin
Continued := True;
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
TThread.Sleep(1000);
end;
end;
procedure TService1.ServicePause(Sender: TService; var Paused: Boolean);
begin
Paused := True;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
// TFile.AppendAllText('log.txt', 'ServiceStart tries to initialize PeriodicThread...')
Started := True;
end;
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
Stopped := True;
end;
The ServiceStart()
, ServiceStop()
, ServicePause()
, ServiceContinue()
and ServiceExecute()
methods are all correctly hooked to corresponding events of TService1
(I can see them in the Object Inspector just fine). But when I try to add a line that would write a message to a log file (now commented out), the message is never written. It's almost like the ServiceStart()
method never gets executed and the service just keeps hanging there in some limbo without even trying to start.
What am I missing?
Edit:
To clarify: I used the article Developing Windows Services in Windows 11: Best Practices and Tools to guide me.
I did try the version with the ServiceThread.ProcessRequests(false);
line in the ServiceExecute()
loop, and I did try even adding the FBackgroundThread
, but the results were always exactly the same.
I'm using Delphi 11 and I'm trying to learn about Windows services.
I've created a very basic minimalistic service that should basically just start and sleep in a loop until stopped. I got most of the code directly from Embarcadero's blog "Developing Windows Services in Windows 11: Best Practices and Tools". But when I install the service with this Powershell command with Administrator access:
sc create TestSvc binPath= "C:\Actual\Path\TestSvc.exe" start= demand
and then try to run it via sc start TestSvc
the outcome is:
SERVICE_NAME: TestSvc TYPE : 10 WIN32_OWN_PROCESS STATE : 2 START_PENDING (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x7d0 PID : 7292 FLAGS :
And it stays forever stuck in the START_PENDING
state, while the Task Manager keeps showing it as:
Starting
My TService1
class has only these few methods:
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service1.Controller(CtrlCode);
end;
function TService1.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TService1.ServiceContinue(Sender: TService; var Continued: Boolean);
begin
Continued := True;
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
TThread.Sleep(1000);
end;
end;
procedure TService1.ServicePause(Sender: TService; var Paused: Boolean);
begin
Paused := True;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
// TFile.AppendAllText('log.txt', 'ServiceStart tries to initialize PeriodicThread...')
Started := True;
end;
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
Stopped := True;
end;
The ServiceStart()
, ServiceStop()
, ServicePause()
, ServiceContinue()
and ServiceExecute()
methods are all correctly hooked to corresponding events of TService1
(I can see them in the Object Inspector just fine). But when I try to add a line that would write a message to a log file (now commented out), the message is never written. It's almost like the ServiceStart()
method never gets executed and the service just keeps hanging there in some limbo without even trying to start.
What am I missing?
Edit:
To clarify: I used the article Developing Windows Services in Windows 11: Best Practices and Tools to guide me.
I did try the version with the ServiceThread.ProcessRequests(false);
line in the ServiceExecute()
loop, and I did try even adding the FBackgroundThread
, but the results were always exactly the same.
2 Answers
Reset to default 9TService
sets its status to csStartPending
before starting its worker ServiceThread
. That worker thread then triggers the OnStart
and OnExecute
events, setting the status to csRunning
after OnStart
and before OnExecute
.
So, getting stuck in a START_PENDING
state would imply that TService
is not able to run its ServiceThread
at all, which should be impossible under normal conditions.
The only way the ServiceThread
could freeze before the OnStart
event is triggered is if Application.DelayInitialize
is true
(it is false
by default), in which case TServiceThread
calls Application.Initialize()
before triggering OnStart
. So, if something inside of Application.Initialize()
would freeze, you would get the behavior you describe.
I've used TService
for over 20 year and have never seen it not run its ServiceThread
or trigger events. So, you are just going to have to debug your service at runtime to see where it is actually freezing up.
That being said, your OnExecute
event handler is not processing any subsequent SCM requests after the OnStart
event. When you assign an OnExecute
handler, you become responsible for handling SCM requests and status updates, eg:
procedure TService1.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(false); // <-- ADD THIS!
TThread.Sleep(1000);
end;
end;
If you simply get rid of the OnExecute
handler completely, then TService
will process all SCM requests for you.
When you are ready to have your service do actual work, it is generally easier to just avoid the OnExecute
event for this very reason. Instead, have your OnStart
handler launch a worker thread for your business logic, and then have your OnStop
and OnShutdown
handlers terminate that thread. Let TService
deal with the SCM, you focus on your business logic.
So I found the solution, even though I'm not 100% sure why it works. When Remy Lebeau pointed out it has to be an initialization issue, I went through dpr file and noticed this bit:
// Application.DelayInitialize := True;
//
if not Application.DelayInitialize or Application.Installing then
Application.Initialize;
As Remy said, DelayInitialize should be false by default and this bit doesn't suggest otherwise (the line that would set it True has been commented out since the begining), so initialization should happen. But being completely out of any better ideas, I decided to inspect the Installing part of the condition. I found it simply ment whether the service exe was run with /install
parameter or not. So I tried that. I used
C:\Actual\Path\TestSvc.exe /install
instead of
sc create TestSvc binPath= "C:\Actual\Path\TestSvc.exe" start= demand
and now I can start it by sc start Service1
without an issue. I later added a background thread to do any work and got rid of ServiceExecute just as suggested and it keeps working fine. So for whatever reason, using different command to install the service did the trick.
I kept asking around and someone said it appears to be a "known issue" with Delphi and TServiceThread, though I couldn't find any mention of it anywhere online.
ProcessRequests
inside theServiceExecute
method. – Damien_The_Unbeliever Commented Feb 7 at 13:43