Delphi Windows 服务设计
Posted
技术标签:
【中文标题】Delphi Windows 服务设计【英文标题】:Delphi Windows Service Design 【发布时间】:2012-05-19 05:14:30 【问题描述】:我从未创建过 Windows 服务,但我一直在阅读我找到的所有内容。我遇到的所有文章或示例在实现方面都非常基础,并且范围有限。还没有看到任何超出这个或解决特定场景的东西。所以,我已经掌握了我可能会找到的所有理论,现在我准备好深入研究这个项目了。我喜欢整理我的想法,并就人们的想法获得一些反馈。我将描述我需要从应用程序中获得什么以及我打算如何构建它。我会感谢任何有构建 Windows 服务经验的人提供的 cmets 以及他们愿意分享的任何建议。
[场景] 现在我有一个应用程序(我称之为 UPDATEAPPLICATION),它为我们所有的其他应用程序提供更新。为了运行我们的任何应用程序,您首先必须运行此 UPDATEAPPLICATION 程序并将所需应用程序的参数传递给它。 UPDATEAPPLICATION 调用一个 WebService,它返回有关所需应用程序是否有任何更新的 XML 信息。
如果有更新,UPDATEAPPLICATION 会以 EXE 或 ZIP 格式下载更新,并替换相应的文件以更新目标应用程序。之后 UPDATEAPPLICATION 执行 ShellExecute 以启动所需的应用程序,然后 UPDATEAPPLICATION 关闭。
这是一个相当基本的过程,多年来一直运行良好。 UPDATEAPPLICATION 程序是一个Delphi 应用程序,我们的其他应用程序是混合的:Delphi、VB6、MS Access、.NET。
[问题] 随着向 Vista 和 Windows 7 的迁移,安全性发生了巨大变化。由于 UPDATEAPPLICATION 的性质,UAC 不允许应用程序在没有管理员访问权限或 UAC 完全关闭的情况下运行。我们正在将我们的许多应用程序升级到 .NET,在此过程中,我希望这些应用程序以及 UPDATEAPPLICATION 符合 UAC。根据我的研究,唯一的方法是将 UPDATEAPPLICATION 创建为 Windows 服务。因此,本质上,我需要将 UPDATEAPPLICATION 的功能复制到 Windows 服务架构中。
[我的设计] 我正在使用 DelphiXE2。我的设计将由 3 部分组成,形成一个单一的解决方案:一个 Windows 服务、一个与 Windows 服务交互的小托盘应用程序,以及我重新设计的将向 Windows 服务发送消息的应用程序。
-
我的 Windows 服务(我将其称为 UPDATESERVICE)将作为 Windows 服务运行并创建一个 TCP 服务器来侦听请求。
托盘应用程序(我将其称为 TRAYAPP)将使用 TCP 客户端来配置/管理 UPDATESERVICE。
我的 USERAPPLICATION 在启动时会向 UPDATESERVICE 发送一条 TCP 消息,上面写着“此应用程序”已启动。
[更新服务] 会收听消息。如果它收到一条 USERAPPLICATION 已启动的消息,它将调用 Web 服务以查看是否有更新。如果有,将通知用户关闭应用程序并允许 UPDATESERVICE 更新应用程序。 UPDATESERVICE 将下载适当的文件并更新应用程序。
现在我已经解释了我正在尝试做的事情的基础知识,我可以提出我需要回答的具体问题。这些都与我应该如何构建我的 Windows 服务有关。我还计划使用 OmniThread 进行线程管理。
当我的服务启动时,我需要创建 TCP 服务器。
-
是否应该在自己的线程上创建 TCP 服务?
如果 TCP 服务是它自己的线程,我如何保持线程处于活动状态?否则,我可以启动 TCP 服务,但我不确定我将在 TCP 服务单元中使用什么代码来保持线程运行?
什么 Windows 服务事件应该创建 TCP 服务?执行?开机?创建?毕竟我读过它还不清楚应该使用什么事件。
当 TCP 服务接收到要执行某项操作的消息时,该工作应该在 TCP 服务线程中执行还是由主 UPDATESERVICE 产生的新线程执行?例如:
如果 TCP 服务收到一条使用 HTTP 检查更新的消息,那么 TCP 服务线程是否应该生成一个新线程来完成这项工作
或者,TCP 服务线程是否应该向 UPDATESERVICE 发送消息以生成一个新线程来完成这项工作
这还重要吗?
是否可以在 Delphi Code 中启动/停止/注册/取消注册 Windows 服务?
这是我所有的问题。对此可能没有正确/错误的答案,而只是基于经验的偏好。如果您已经使用 Delphi 构建了服务,您可能会有一些我觉得有用的输入。如果您有一个比基本的“启动服务并休眠”更强大的项目并且愿意分享它——即使我不运行或只是伪代码——我相信这将是无价的。感谢您阅读我冗长的问题。如果您能想到更好的解决方法,请分享您的想法。我要补充一点,我们的一些应用程序可以由公众下载和运行,因此我无法完全控制预期的环境。任何建议/cmets/帮助将不胜感激。
【问题讨论】:
【参考方案1】:快速回答:
1&3) 是的。根据经验,不要实现 OnExecute 服务事件。从 OnStart 服务事件中生成您自己的线程。当您收到 OnStop 服务事件时,可以终止线程。
2)你让你的线程像这样(执行方法):
while not Terminated do
begin
// do something
end;
4) 通常每个客户端连接都将存在于它自己的线程上。 (即 TCP 服务器为每个客户端生成一个新线程)。使用众所周知的堆栈,例如 Indy 或 ICS。关于 HTTP 更新,您可以在生成的客户端连接线程中执行此操作。
5) 是的,请注意您需要提升权限才能执行此操作。
在我的职业生涯中,我做过不少服务,到目前为止,我一直使用相同的框架来构建服务应用程序:
unit u_svc_main;
interface
uses
// Own units
u_globals, u_eventlog, u_MyThread,
// Third party units
// Delphi units
Windows, Messages, Registry, SysUtils, Classes, SvcMgr;
type
TMyService = class(TService)
procedure ServiceCreate(Sender: TObject);
procedure ServiceAfterUninstall(Sender: TService);
procedure ServiceAfterInstall(Sender: TService);
procedure ServiceShutdown(Sender: TService);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceStart(Sender: TService; var Started: Boolean);
private
Private declarations
MyThread : TMyThread;
public
Public declarations
function GetServiceController: TServiceController; override;
end;
var MyService : TMyService;
implementation
$R *.DFM
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
MyService.Controller(CtrlCode);
end;
function TMyService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TMyService.ServiceCreate(Sender: TObject);
begin
DisplayName := 'myservice';
end;
procedure TMyService.ServiceAfterInstall(Sender: TService);
var
Reg : TRegistry;
ImagePath : string;
begin
// create needed registry entries after service installation
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
// set service description
if Reg.OpenKey(STR_REGKEY_SVC,False) then
begin
ImagePath := Reg.ReadString(STR_REGVAL_IMAGEPATH);
Reg.WriteString(STR_REGVAL_DESCRIPTION, STR_INFO_SVC_DESC);
Reg.CloseKey;
end;
// set message resource for eventlog
if Reg.OpenKey(STR_REGKEY_EVENTMSG, True) then
begin
Reg.WriteString(STR_REGVAL_EVENTMESSAGEFILE, ImagePath);
Reg.WriteInteger(STR_REGVAL_TYPESSUPPORTED, 7);
Reg.CloseKey;
end;
// set installdir
if ImagePath <> '' then
if Reg.OpenKey(STR_REGKEY_FULL,True) then
begin
Reg.WriteString(STR_REGVAL_INSTALLDIR, ExtractFilePath(ImagePath));
Reg.CloseKey;
end;
finally
FreeAndNil(Reg);
end;
end;
procedure TMyService.ServiceAfterUninstall(Sender: TService);
var
Reg : TRegistry;
begin
Reg := TRegistry.Create;
try
// delete self created registry keys
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.DeleteKey(STR_REGKEY_EVENTMSG);
finally
FreeAndNil(Reg);
end;
end;
procedure TMyService.ServiceShutdown(Sender: TService);
var
Stopped : boolean;
begin
// is called when windows shuts down
ServiceStop(Self, Stopped);
end;
procedure TMyService.ServiceStart(Sender: TService; var Started: Boolean);
begin
Started := False;
try
MyThread := TMyThread.Create;
MyThread.Resume;
NTEventLog.Add(Eventlog_Success, STR_INFO_SVC_STARTED);
Started := True;
except
on E : Exception do
begin
// add event in eventlog with reason why the service couldn't start
NTEventLog.Add(Eventlog_Error_Type, Format(STR_INFO_SVC_STARTFAIL, [E.Message]));
end;
end;
end;
procedure TMyService.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
try
Stopped := True; // always stop service, even if we had exceptions, this is to prevent "stuck" service (must reboot then)
MyThread.Terminate;
// give MyThread 60 seconds to terminate
if WaitForSingleObject(MyThread.ThreadEvent, 60000) = WAIT_OBJECT_0 then
begin
FreeAndNil(MyThread);
NTEventLog.Add(Eventlog_Success,STR_INFO_SVC_STOPPED);
end;
except
on E : Exception do
begin
// add event in eventlog with reason why the service couldn't stop
NTEventLog.Add(Eventlog_Error_Type, Format(STR_INFO_SVC_STOPFAIL, [E.Message]));
end;
end;
end;
end.
【讨论】:
甜蜜。感谢您花时间回复。这是我一直在寻找的实用类型的输入。您的骨架代码充实了起点,这比我迄今为止发现的大部分内容要好得多。你的其他答案也是我所期待的。很高兴听到有人从这条路上走过。再次感谢。 我也有类似的情况,正在创建我的第一个 Windows 服务,我发现您在这里的回复很有帮助。谢谢分享。我还有两个问题:在 ServiceAfterInstall 中,注册表项是在“// set installdir”注释之后写入的 - 它的目的是什么? - 该条目的正确 REGKEY 和 REGVAL 是什么? @NevilleCook,STR_REGKEYFULL 是您的应用程序的 HKLM 注册表项,类似于 HKLM\Software\MyCompany\MyApp,STR_REGVAL_INSTALLDIR 将表示“InstallDir”键 = 您的服务的安装路径。你真的不需要它,你可以随时读取服务的 ImagePath 键来确定安装路径(就像我在 AfterInstall 中所做的那样)。 @whosrdaddy,感谢您的回复。现在我知道这不是为了任何特定目的,我可以更有信心地前进。 @RemyLebeau,ImagePath
的内容出现在哪里?以上是关于Delphi Windows 服务设计的主要内容,如果未能解决你的问题,请参考以下文章