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 服务设计的主要内容,如果未能解决你的问题,请参考以下文章

DELPHI下的多线程程序设计

Windows 32 程序设计

delphi怎样编写服务程序

DELPHi分布式多层设计,的瘦客户机的理解

Delphi学习技巧

用Delphi创建windows服务程序