fpsimpleservice.pp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by the Free Pascal development team
  4. Simple service application class for windows.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit fpsimpleservice;
  12. {
  13. Application checks following command-line options to determine behaviour:
  14. -r run service
  15. -i install service
  16. -u uninstall service.
  17. When the service is run, a descendent of TFPServiceThread is created and executed.
  18. You must set the descendant class to use in Application.ServiceClass before calling initialize.
  19. }
  20. {$mode objfpc}{$H+}
  21. interface
  22. uses
  23. Classes, SysUtils, custapp, windows, eventlog, jwawinsvc;
  24. Type
  25. { TFPServiceThread }
  26. TFPServiceThread = Class (TThread)
  27. Private
  28. FPaused : Boolean;
  29. protected
  30. Procedure HandleControlCode(Command :Integer); virtual;
  31. Procedure Log(EventType : TEventType; const Msg : String);
  32. Procedure PauseService; virtual;
  33. Procedure StopService; virtual;
  34. Procedure RunService; virtual;
  35. Procedure ContinueService; virtual;
  36. Procedure Execute; override;
  37. property Paused : Boolean Read FPaused;
  38. end;
  39. TFPServiceThreadClass = Class of TFPServiceThread;
  40. { TFPServiceApplication }
  41. TFPServiceApplication = Class(TCustomApplication)
  42. private
  43. FAllowServicePause: Boolean;
  44. FServiceClass: TFPServiceThreadClass;
  45. FServiceParamStr : string;
  46. FTimeout,
  47. FExitCode,
  48. FServiceParamCount : integer;
  49. FStatus : TServiceStatus;
  50. FStopEvent : THandle;
  51. FReportStartStop : boolean;
  52. FStatusHandle : Service_Status_Handle;
  53. FServiceThread : TFPServiceThread;
  54. FChkPoint : Integer;
  55. FEventLog : TEventLog;
  56. procedure ServiceController(Command: Integer);
  57. procedure ServiceMain(ArgC: integer; ArgV: ppchar);
  58. procedure StopNow;
  59. function ReportNoError(AState : integer) : boolean;
  60. function ReportServiceStatus(CurrentState, Win32ExitCode, CheckPoint, WaitHint: integer): boolean;
  61. Protected
  62. function ConnectToServiceManager: SC_Handle;
  63. procedure DoRun; override;
  64. Procedure RunService;
  65. Procedure InstallService;
  66. Procedure UninstallService;
  67. Procedure DoLog(EventType : TEventType; const Msg : String); override;
  68. public
  69. Constructor Create(AOwner : TComponent); override;
  70. destructor Destroy; override;
  71. // Return service error code.
  72. function GetServiceError: integer;
  73. // Return service error message.
  74. function GetServiceErrorMessage: string;
  75. // Report that a start is pending. Return true on success.
  76. function ReportStartPending: boolean;
  77. // Report that a stop is pending. Return true on success.
  78. function ReportStopPending: boolean;
  79. // Application start
  80. procedure Initialize; override;
  81. // Thread class to use when starting the service.
  82. Property ServiceClass : TFPServiceThreadClass Read FServiceClass Write FServiceClass;
  83. // Time before to generate an error. Default 20000 milliseconds
  84. property Timeout : integer read FTimeout write FTimeout;
  85. // Exit code to return to Service Manager
  86. property ExitCode : integer read FExitCode write FExitCode;
  87. // Parameter list passed when the service was started
  88. property ServiceParamStr : string read FServiceParamStr;
  89. // Number of parameters passed when the service was started
  90. property ServiceParamCount : integer read FServiceParamCount;
  91. // Does the service accept pause/continue commands ?
  92. Property AllowServicePause : Boolean Read FAllowServicePause Write FAllowServicePause;
  93. end;
  94. EFPService = Class(Exception);
  95. function Application: TFPServiceApplication;
  96. implementation
  97. Resourcestring
  98. SErrNoServiceClass = 'Cannot run: No service thread class registered.';
  99. SErrRunNeedsOverride = 'Cannot run: Runservice must be overridden';
  100. SErrRunningService = 'Error running service: %s';
  101. SControlCodeReceived = 'Service: Received control code %d';
  102. SServicePaused = 'Service received pause command.';
  103. SServiceContinued = 'Service received continue command.';
  104. procedure ServiceControllerEntry(Command : DWord); stdcall;
  105. begin
  106. Application.ServiceController(Command);
  107. end;
  108. procedure ServiceMainEntry(ArgC : DWord; ArgV : pchar); stdcall;
  109. begin
  110. Application.ServiceMain(ArgC,PPchar(ArgV));
  111. end;
  112. Var
  113. App : TFPServiceApplication;
  114. function Application: TFPServiceApplication;
  115. begin
  116. If (App=Nil) then
  117. App:=TFPServiceApplication.Create(Nil);
  118. Result:=App;
  119. end;
  120. { TFPServiceThread }
  121. procedure TFPServiceThread.HandleControlCode(Command: Integer);
  122. begin
  123. Log(etInfo,Format(SControlCodeReceived,[Command]));
  124. end;
  125. procedure TFPServiceThread.Log(EventType: TEventType; const Msg: String);
  126. begin
  127. If Assigned(App) then
  128. App.Log(EventType,Msg);
  129. end;
  130. procedure TFPServiceThread.PauseService;
  131. begin
  132. Log(etInfo,SServicePaused);
  133. Suspend;
  134. end;
  135. procedure TFPServiceThread.ContinueService;
  136. begin
  137. Resume;
  138. Log(etInfo,SServiceContinued);
  139. end;
  140. procedure TFPServiceThread.StopService;
  141. begin
  142. Terminate;
  143. end;
  144. procedure TFPServiceThread.RunService;
  145. begin
  146. Raise EFPService.Create(SErrRunNeedsOverride);
  147. end;
  148. { TFPServiceApplication }
  149. function TFPServiceApplication.ReportServiceStatus(CurrentState,
  150. Win32ExitCode, CheckPoint, WaitHint: integer): boolean;
  151. begin
  152. SetLastError(0);
  153. With FStatus do
  154. begin
  155. dwServiceType := SERVICE_WIN32_OWN_PROCESS;
  156. dwServiceSpecificExitCode := 0;
  157. if CurrentState = SERVICE_START_PENDING then
  158. dwControlsAccepted := 0
  159. else
  160. begin
  161. dwControlsAccepted := SERVICE_ACCEPT_STOP;
  162. if FAllowServicePause then
  163. dwControlsAccepted:=dwControlsAccepted or SERVICE_ACCEPT_PAUSE_CONTINUE;
  164. end;
  165. dwCurrentState:=CurrentState;
  166. dwCheckPoint:=CheckPoint;
  167. dwWaitHint:=WaitHint;
  168. if (ExitCode=0) then
  169. dwWin32ExitCode := Win32ExitCode
  170. else
  171. begin
  172. dwWin32ExitCode := ERROR_SERVICE_SPECIFIC_ERROR;
  173. dwServiceSpecificExitCode := ExitCode;
  174. end;
  175. end;
  176. Result:=SetServiceStatus(FStatusHandle, FStatus);
  177. if not Result then
  178. StopNow;
  179. end;
  180. procedure TFPServiceApplication.DoRun;
  181. begin
  182. If HasOption('r','run') then
  183. RunService
  184. else if HasOption('i','install') then
  185. InstallService
  186. else if HasOption('u','uninstall') then
  187. UninstallService
  188. else
  189. Inherited;
  190. Terminate;
  191. end;
  192. procedure TFPServiceApplication.InstallService;
  193. Var
  194. S : string;
  195. FManager,
  196. FService : SC_Handle;
  197. begin
  198. S:=ParamStr(0)+' -r';
  199. If HasOption('c','config') then
  200. S:=S+' -c '+self.GetOptionValue('c','config');
  201. try
  202. FManager:=ConnectToServiceManager;
  203. try
  204. FService := CreateService(FManager, PChar(Name), Pchar(Title), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
  205. SERVICE_ERROR_NORMAL, pchar(S), nil, nil, nil, nil, nil);
  206. if (FService=0) then
  207. RaiseLastOSError;
  208. CloseServiceHandle(FService);
  209. finally
  210. CloseServiceHandle(FManager);
  211. end;
  212. finally
  213. Terminate;
  214. end;
  215. end;
  216. Function TFPServiceApplication.ConnectToServiceManager : SC_Handle;
  217. begin
  218. Result:=OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  219. if (Result=0) then
  220. RaiseLastOSError;
  221. end;
  222. procedure TFPServiceApplication.UninstallService;
  223. Var
  224. FManager,
  225. FService : SC_Handle;
  226. begin
  227. try
  228. FManager:=ConnectToServiceManager;
  229. try
  230. FService:=OpenService(FManager, Pchar(Name), SERVICE_ALL_ACCESS);
  231. if (FService=0) then
  232. RaiseLastOSError;
  233. if not DeleteService(FService) then
  234. RaiseLastOSError;
  235. CloseServiceHandle(FService);
  236. finally
  237. CloseServiceHandle(FManager);
  238. end;
  239. finally
  240. Terminate;
  241. end;
  242. end;
  243. procedure TFPServiceApplication.DoLog(EventType: TEventType; const Msg: String);
  244. begin
  245. FeventLog.Log(EventType,Msg);
  246. end;
  247. constructor TFPServiceApplication.Create(AOwner: TComponent);
  248. begin
  249. inherited Create(AOwner);
  250. FEventLog:=TEventLog.Create(Self);
  251. FEventLog.RegisterMessageFile(ParamStr(0));
  252. StopOnException:=True;
  253. end;
  254. function TFPServiceApplication.GetServiceError : integer;
  255. begin
  256. Result := GetLastError;
  257. if Result = 0 then
  258. Result := -1
  259. end;
  260. // Returns last error message
  261. function TFPServiceApplication.GetServiceErrorMessage : string;
  262. begin
  263. Result := SysErrorMessage(GetServiceError)
  264. end;
  265. procedure TFPServiceApplication.StopNow;
  266. begin
  267. SetLastError(0);
  268. SetEvent(FStopEvent)
  269. end;
  270. function TFPServiceApplication.ReportNoError(AState : integer) : boolean;
  271. begin
  272. Result:=ReportServiceStatus(AState, NO_ERROR, 0, 0)
  273. end;
  274. function TFPServiceApplication.ReportStartPending : boolean;
  275. begin
  276. Inc(FChkPoint);
  277. Result := ReportServiceStatus(SERVICE_START_PENDING, NO_ERROR, FChkPoint, Timeout);
  278. end;
  279. function TFPServiceApplication.ReportStopPending : boolean;
  280. begin
  281. Inc(FChkPoint);
  282. Result := ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, FChkPoint, Timeout);
  283. end;
  284. Procedure TFPServiceApplication.ServiceController(Command :Integer);
  285. begin
  286. case Command of
  287. SERVICE_CONTROL_PAUSE:
  288. if FStatus.dwCurrentState = SERVICE_RUNNING then
  289. begin
  290. FServiceThread.FPaused:=True;
  291. FServiceThread.PauseService;
  292. ReportNoError(SERVICE_PAUSED);
  293. end;
  294. SERVICE_CONTROL_CONTINUE:
  295. if FStatus.dwCurrentState = SERVICE_PAUSED then
  296. begin
  297. FServiceThread.FPaused:=False;
  298. FServiceThread.ContinueService;
  299. ReportNoError(SERVICE_RUNNING);
  300. end;
  301. SERVICE_CONTROL_STOP:
  302. begin
  303. ReportStopPending;
  304. If Assigned(FServiceThread) then
  305. begin
  306. FServiceThread.StopService;
  307. ReportStopPending;
  308. end;
  309. ReportStopPending;
  310. StopNow;
  311. end;
  312. SERVICE_CONTROL_INTERROGATE:
  313. ReportNoError(SERVICE_RUNNING);
  314. else
  315. FServiceThread.HandleControlCode(Command);
  316. end;
  317. end;
  318. procedure TFPServiceApplication.ServiceMain(ArgC : integer; ArgV : ppchar);
  319. begin
  320. FServiceParamCount := ArgC;
  321. if (ArgV<>Nil) then
  322. FServiceParamStr := strpas(ArgV^);
  323. SetLastError(0);
  324. FStatusHandle := RegisterServiceCtrlHandlerA(PChar(Name),@ServiceControllerEntry);
  325. if FStatusHandle <> 0 then
  326. begin
  327. if ReportStartPending then
  328. begin
  329. SetLastError(0);
  330. FStopEvent := CreateEvent(nil, true, false, nil);
  331. if FStopEvent <> 0 then
  332. begin
  333. ReportStartPending;
  334. FServiceThread:=FServiceClass.Create(False);
  335. // Wait for stop signal
  336. if ReportNoError(SERVICE_RUNNING) then
  337. begin
  338. {$ifdef svcdebug} DebugLog('Starting wait for stop event');{$endif svcdebug}
  339. WaitForSingleObject(FStopEvent, INFINITE);
  340. {$ifdef svcdebug} DebugLog('End wait for stop event');{$endif svcdebug}
  341. end;
  342. ReportStopPending;
  343. SetLastError(0);
  344. CloseHandle(FStopEvent);
  345. end;
  346. end;
  347. end;
  348. ReportServiceStatus(SERVICE_STOPPED, GetLastError, 0, 0);
  349. end;
  350. Procedure TFPServiceApplication.RunService;
  351. var
  352. SvcTbl : array[0..1] of TServiceTableEntry;
  353. begin
  354. if (FServiceClass=nil) then
  355. Raise EFPService.Create(SErrNoServiceClass);
  356. FEventLog.Identification:='Service '+Name;
  357. FeventLog.Active:=True;
  358. fillchar(SvcTbl, sizeof(SvcTbl),0);
  359. SvcTbl[0].lpServiceName := Pchar(Name);
  360. SvcTbl[0].lpServiceProc := @ServiceMainEntry;
  361. SetLastError(0);
  362. // Returns only when the service stops
  363. StartServiceCtrlDispatcher(@SvcTbl[0]);
  364. end;
  365. procedure TFPServiceApplication.Initialize;
  366. begin
  367. FTimeout := 20000;
  368. FReportStartStop := true;
  369. end;
  370. destructor TFPServiceApplication.Destroy;
  371. begin
  372. FreeAndNil(FEventLog);
  373. Inherited;
  374. end;
  375. procedure TFPServiceThread.Execute;
  376. begin
  377. try
  378. try
  379. RunService;
  380. finally
  381. Terminate;
  382. end;
  383. except
  384. On E : Exception do
  385. Log(etError,Format(SerrRunningService,[E.Message]));
  386. end;
  387. end;
  388. initialization
  389. Finalization
  390. FreeAndNil(App);
  391. end.