Delphi Windows服务设计

21

Delphi Windows服务设计

我之前从未创建过Windows服务,但一直在阅读相关资料。我看到的所有文章或示例都是非常基础的实现和受限制的范围,没有看到任何超越这些或处理特定情况的内容。因此,我已经了解了所有理论知识,并准备深入研究这个项目。我喜欢将我的想法布局出来,并获得人们的反馈意见。我将描述我需要应用程序提供的功能以及我打算如何构建它。我希望得到有经验的Windows服务构建者的评论和任何他们愿意分享的建议。

[场景] 目前我有一个应用程序(我将其称为UPDATEAPPLICATION),它为我们的其他应用程序提供更新。要运行我们的任何应用程序,您必须首先运行此UPDATEAPPLICATION程序并向其传递所需应用程序的参数。UPDATEAPPLICATION调用Web服务,返回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服务发送消息。

  1. 我的Windows服务(我将其称为UPDATESERVICE)将作为Windows服务运行,并创建一个TCP服务器来侦听请求。
  2. 托盘应用程序(我将其称为TRAYAPP)将使用TCP客户端来配置/管理UPDATESERVICE。
  3. 当启动我的USERAPPLICATION时,它将向UPDATESERVICE发送一个TCP消息,说明“此应用程序”已经启动。

[UPDATESERVICE] 将侦听消息。如果收到一个消息,说有一个USERAPPLICATION已经启动,将调用Web服务查看是否有更新。如果有更新,用户将被通知关闭应用程序并允许UPDATESERVICE更新应用程序。UPDATESERVICE将下载适当的文件并更新应用程序。

现在我已经解释了我要做的基础知识,我可以问我的具体问题了。这些问题都与如何构建我的Windows服务有关。我还计划使用OmniThread进行线程管理。

当我的服务启动时,我需要创建TCP服务器。

  1. 应该将TCP服务创建在自己的线程上吗?
  2. 如果TCP服务是它自己的线程,如何让线程保持活动状态?否则,我可以启动TCP服务,但我不确定在TCP服务单元内应使用什么代码来使线程保持运行状态?
  3. 哪个Windows Services事件应该创建TCP Service?OnExecute?OnStart?OnCreate?根据我所读的,还不清楚应该使用哪个事件。
  4. 当TCP Service收到要执行某些操作的消息时,工作应该在TCP Service线程内执行,还是在主UPDATESERVICE中产生一个新的线程?例如:
    • 如果TCP Service收到一个使用HTTP检查更新的消息,是否应该在TCP Service线程内产生一个新的线程来执行此操作
    • 或者,TCP服务线程是否应向UPDATESERVICE发送消息以生成一个新线程来完成此工作
    • 这真的很重要吗?
  5. 是否可以在Delphi代码中启动/停止/注册/取消注册Windows服务?

这是我的所有问题。可能没有正确/错误的答案,只是基于经验的偏好。如果您使用Delphi构建了服务,则可能有一些对我有用的输入。如果您有一个比基本的“启动服务并睡眠”更强大的项目,并愿意共享它——即使我无法运行或只是伪代码——我相信这将是非常有价值的。谢谢您阅读我冗长的问题。如果您能想出更好的方法,请分享您的想法。我要补充说,我们的几个应用程序可以供公众下载和运行,因此我不能完全控制预期的环境。任何建议/评论/帮助都将不胜感激。

1个回答

31

快速回答:

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.

2
太好了。感谢你抽出时间回复。这是我寻找的实用输入类型。你的骨架代码是一个很好的起点,比我迄今为止找到的大部分内容都要好得多。你的其他答案也是我期望的,只是很高兴从曾经走过这条路的人那里听到。再次感谢。 - Darren -
1
@NevilleCook,STR_REGKEYFULL是您的应用程序的HKLM注册表键,类似于HKLM\Software\MyCompany\MyApp,STR_REGVAL_INSTALLDIR代表键“InstallDir”=服务安装路径。您实际上不需要它,您可以始终读取服务的ImagePath键以确定安装路径(就像我在AfterInstall中所做的那样)。 - whosrdaddy
@whosrdaddy,感谢您的回复。现在我知道它不是为任何特定目的而设计的,我可以更加自信地继续前进了。 - Neville Cook
@RemyLebeau,ImagePath的内容出现在哪里? - NaN
@EASI:我不明白你在问什么。ImagePath是由SCM在服务安装时创建的。代码也可以轻松地使用ParamStr(0)来获取调用进程的路径和文件名。 - Remy Lebeau
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接