当你开发Windows软件时,你会遇到某些种类的应用程序需要24小时运行,或者,事实上,在计算机运行时持续不断地运行。通常情况下,这些计算机是网络服务器或台式机上的监控应用程序。在这些情况下,你可能会考虑创建一个控制台应用程序,它要么有最小的交互量,要么完全无声,但有比这更好的解决方案。
简单的控制台或最小化的普通GUI应用程序可能面临Windows会话终止、重启和用户权限等问题。解决这个问题的方法是开发Windows服务。在本教程中,我们将深入研究使用Delphi构建Windows服务。在这之后,你可以把它作为你的Windows服务的模板。
目录
为什么我需要创建一个Windows服务?
创建长期运行的服务有许多原因,如::
- 处理CPU密集型数据。
- 在后台排队的工作项目。
- 在时间表上执行基于时间的操作
- 对 "助手 "或实用程序功能进行不引人注目的操作
后台服务处理通常不涉及用户界面(UI),但可以围绕它们建立UI。
开发Windows服务的先决条件是什么?
在开始潜心研究服务应用开发之前,我建议你看看最新版本的Delphi IDE。有了大量的增强功能和新特性,开发过程会更加顺利。此外,你可以免费使用Delphi社区版,熟悉Delphi编程语言和它的语法。
Windows服务应用程序是如何工作的?
服务应用程序从客户端应用程序接收请求,处理这些请求,并将信息返回给客户端应用程序。一个Web、FTP或电子邮件服务器就是一个服务应用程序的例子。
一个Windows服务应用程序是一个可以运行而不需要用户登录的Windows应用程序。Windows服务应用程序很少与桌面互动。在本教程中,我们将用Delphi创建一个Windows服务应用程序。
当一个服务程序运行时,它可以被设置为使用一套默认的用户权限,既可以作为一个虚拟的 "服务用户",也可以作为一个实际的具有访问系统权限的普通用户。重要的是,这些用户权限会影响到你的服务应用程序有权访问的文件夹,以及任何 "映射 "的网络文件夹--如果有人创建了一个映射,比如一个指向网络文件夹的 "Z: "驱动器很可能无法使用。在可能的情况下,你应该总是使用完整的UNC路径名称--或者使用Windows系统调用或Delphi的运行时TPath类型函数来获得特殊文件夹的正确位置,如%APPDATA%文件夹的位置和 "我的文档 "类型的虚拟路径。
Windows服务应用程序可以提供很多功能。当你启动服务实用工具时,请看列表。这些服务可以成为你使用的主要GUI应用程序的核心。
你什么时候需要编写Windows服务?
前段时间,我需要一个系统监控工具来监控我们文件服务器上的可用磁盘空间。我写了一个工具,每分钟检查一次,然后把这些信息写到一个日志文件中。然而,它需要一个用户登录,而当用户退出时,我的应用程序就关闭了。解决办法是将该程序重新创建为一个Windows服务,然后在计算机开机时一直运行,即使用户没有登录。
虽然RAD Studio与Delphi或C++ Builder对典型的面向用户的交互式应用程序进行了优化,但它更有能力轻松创建服务应用程序。
如何在Delphi中创建一个Windows服务项目?
要在Delphi中创建一个新的项目,你需要在你的计算机上安装Delphi开发环境。一旦你安装并运行了Delphi,你就可以开始创建一个新的项目。
要在RAD Studio中用Delphi创建一个新的Windows服务项目,采取以下步骤:
- 点击文件菜单,选择新建->其他。这将弹出 "新建项目 "对话框。
- 在对话框的左侧,选择Delphi,然后从Windows类别中选择Windows服务项目类型。
- 点击 "确定 "来创建新项目。
请注意,对于那些使用C++ Builder的人来说,这个过程是类似的。
一旦新项目被创建,你将需要设置项目属性和选项。要做到这一点,在 "项目管理器 "窗口中右击项目名称,选择 "选项"。这将弹出 "项目选项 "对话框。在这个对话框中,你可以为你的项目设置各种选项,如目标平台、输出目录和编译器选项。在继续开发你的服务之前,请确保根据你的项目要求设置这些选项。
如何用Delphi实现一个Windows服务应用?
在Delphi中创建一个新的Windows服务项目并设置项目属性和选项后,下一步是实现服务本身。这涉及到向主要的服务单元添加代码,该单元通常被默认为 "Unit1.pas"。
要开始,在 "项目管理器 "窗口中双击 "Unit1.pas "文件,在代码编辑器中打开它。这将显示该单元的代码文件,它包含了Delphi服务应用程序的骨架代码。
要在单元中添加自己的代码,你需要通过处理各种服务事件来定义服务的行为。这些事件包括启动、停止和暂停事件,它们分别在服务启动、停止或暂停时被触发。
为了处理这些事件,你可以使用Delphi内置的服务组件,"TService "类。这个组件提供了各种方法和属性,你可以用它们来控制服务的行为,比如 "开始 "和 "停止 "方法。
例如,你可以使用 "OnStart "事件处理程序来定义服务启动时应该执行的代码。要做到这一点,你可以在 "Unit1.pas "文件中添加以下代码:
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
// code you want to execute when the service starts
// such as making a log entry
end;
除了启动和停止事件外,你可能还需要处理暂停事件,它是在服务暂停时触发的。要做到这一点,你可以使用 "OnPause "事件处理程序,它类似于 "OnStart "和 "OnStop "事件处理程序。
例如,你可以在 "Unit1.pas "文件中添加以下代码来处理暂停事件:
procedure TService1.ServicePause(Sender: TService; var Paused: Boolean);
begin
// Add your code here for things to happen when the user
// chooses "pause" from the service menu or uses the service control
// commands to pause your service.
// This is NOT the same as the service being stopped!
end;
在每一个事件处理程序中,你可以添加你自己的代码来定义当各自事件被触发时服务的行为。例如,你可以使用 "TService "组件的 "Start "和 "Stop "方法来启动或停止一个定时器或一个线程,或者你可以使用 "Pause "方法来暂停一个任务的执行。
一旦你将代码添加到主服务单元并处理了服务的事件,你就可以使用Delphi调试器和Windows服务管理器继续调试和测试服务。
我们如何实现Windows服务应用功能?
在单元的Uses子句中添加Vcl.SvcMgr单元。
通过修改TForm类的声明,将主窗体的祖先改为TService,如下
type
TService1 = class(TService)
这里有完整的源代码:
unit ServiceUnit;
interface
uses
Winapi.Windows
, Winapi.Messages
, System.SysUtils
, System.Classes
, Vcl.Graphics
, Vcl.Controls
, Vcl.SvcMgr
, Vcl.Dialogs
, BackgroundThreadUnit
, System.Win.Registry;
type
TService1 = class(TService)
procedure ServiceExecute(Sender: TService);
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServicePause(Sender: TService; var Paused: Boolean);
procedure ServiceContinue(Sender: TService; var Continued: Boolean);
procedure ServiceAfterInstall(Sender: TService);
private
FBackgroundThread: TBackgroundThread;
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
{$R *.dfm}
var
MyService: TService1;
implementation
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
MyService.Controller(CtrlCode);
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(false);
TThread.Sleep(1000);
end;
end;
function TService1.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TService1.ServiceContinue(Sender: TService;
var Continued: Boolean);
begin
FBackgroundThread.Continue;
Continued := True;
end;
procedure TService1.ServiceAfterInstall(Sender: TService);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_READ or KEY_WRITE);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('SYSTEMCurrentControlSetServices' + name, false) then
begin
Reg.WriteString('Description', 'Blogs.Embarcadero.com');
Reg.CloseKey;
end;
finally
Reg.Free;
end;
end;
procedure TService1.ServicePause(Sender: TService; var Paused: Boolean);
begin
FBackgroundThread.Pause;
Paused := True;
end;
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
FBackgroundThread.Terminate;
FBackgroundThread.WaitFor;
FreeAndNil(FBackgroundThread);
Stopped := True;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
FBackgroundThread := TBackgroundThread.Create(True);
FBackgroundThread.Start;
Started := True;
end;
end.
这里的后台线程代码如下:
unit BackgroundThreadUnit;
interface
uses
System.Classes;
type
TBackgroundThread = class(TThread)
private
FPaused: Boolean;
// FTerminated: Boolean;
// FOnTerminate: TNotifyEvent;
protected
procedure Execute; override;
public
procedure Pause;
procedure Continue;
// procedure Terminate;
// property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
end;
implementation
uses
System.SysUtils, System.IOUtils;
procedure TBackgroundThread.Continue;
begin
FPaused := False;
end;
// process something here
procedure TBackgroundThread.Execute;
var
LogFile: TextFile;
begin
try
FPaused := False;
AssignFile(LogFile, 'C:TempLogs.log');
Rewrite(LogFile);
while not Terminated do
begin
if not FPaused then
begin
WriteLn(LogFile, 'Logs From Background Thread: ' + DateTimeToStr(Now));
end;
TThread.Sleep(1000);
end;
finally
CloseFile(LogFile);
end;
end;
procedure TBackgroundThread.Pause;
begin
FPaused := True;
end;
end.
现在你可以建立服务了。在项目窗口,你可以打开上下文菜单,像这样建立服务:
Delphi构建Windows服务
使用Windows服务管理器测试服务
你可以使用Windows服务管理器来测试服务。这个工具允许你启动、停止、暂停和恢复一个服务,并查看其状态和可能遇到的任何错误。
要安装该服务,你应该遵循以下步骤:
- 进入文件夹,在终端打开(可执行文件应配置为以管理员权限运行)
- 用"/install "命令输入服务名称
进入该文件夹并在终端打开
使用终端安装Windows服务
一旦服务被安装,你可以使用Windows服务管理器启动、停止、暂停或恢复该服务。要做到这一点,在Windows控制面板的管理工具文件夹中打开服务应用程序。在服务列表中找到该服务,右键单击它,并从上下文菜单中选择所需的操作。
在这里你可以启动、停止和暂停服务
几秒钟后,从服务管理器中停止该服务并检查日志文件。
附录:
调试服务应用程序
你可以在服务应用程序已经运行的情况下,通过附加到服务应用程序进程来调试服务应用程序(也就是说,先启动服务,然后附加到调试器上)。要附加到服务应用程序进程,选择 Run > Attach To Process,并在出现的对话框中选择服务应用程序。
在某些情况下,由于权限不足,这种方法可能会失败。如果发生这种情况,你可以使用服务控制管理器来使你的服务能够与调试器一起工作:
要进行调试:
- 首先在以下注册表位置创建一个名为图像文件执行选项的键: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
- 创建一个与你的服务同名的子键(例如,MYSERV.EXE)。在这个子键上,添加一个REG_SZ类型的值,名为Debugger。使用bds.exe的完整路径作为字符串值。
- 在服务控制面板小程序中,选择你的服务,点击启动并勾选允许服务与桌面互动。
对于Windows NT:
在Windows NT系统上,你可以使用另一种方法来调试服务应用程序。然而,这种方法可能很棘手,因为它需要很短的时间间隔:
- 首先,在调试器中启动该应用程序。等待几秒钟,直到它完成加载。
- 从控制面板或命令行中快速启动服务:启动MyServ
你必须快速启动服务(在应用程序启动后15-30秒内),因为如果没有启动服务,应用程序将终止。
How to Delete a Windows Service?
Open PowerShell as an administrator and type this command sc delete ServiceName
How do I silently install a Windows service app?
You can avoid the message box appearing by adding “/silent
” to the end of the command line like so: myserviceapp.exe /install /silent
Can 32bit services run on 64bit Windows?
Yes, a 32bit service can run normally on a 64bit version of Windows. Note, however, that some network system administrators may have a Windows group policy option set up which will prevent anything other than 64bit services from running. Most don’t do this, but it is possible.