21

DelphiWindowsサービスデザイン

私はWindowsサービスを作成したことはありませんが、見つけたものをすべて読んでいます。私が遭遇したすべての記事または例は、実装が非常に基本的であり、その範囲が制限されています。これを超えるものや特定のシナリオに対処するものは見たことがありません。だから、私はおそらく見つけようとしているすべての理論を持っています、そして今私はこのプロジェクトに飛び込む準備ができています。私は自分のアイデアをレイアウトし、人々の考えについてフィードバックを得るのが好きです。アプリケーションに必要なものと、それをどのように構築するつもりかについて説明します。Windowsサービスの構築経験のある方からのコメントや、共有したいアドバイスをいただければ幸いです。

[シナリオ]現在、他のすべてのアプリケーションに更新を提供するアプリケーション(これをUPDATEAPPLICATIONと呼びます)があります。アプリケーションを実行するには、まずこのUPDATEAPPLICATIONプログラムを実行し、目的のアプリケーションのパラメーターを渡す必要があります。UPDATEAPPLICATIONは、目的のアプリケーションに更新があるかどうかに関するXML情報を返すWebServiceを呼び出します。

更新がある場合、UPDATEAPPLICATIONは更新をEXEまたはZIP形式でダウンロードし、適切なファイルを置き換えて対象のアプリケーションを更新します。その後、UPDATEAPPLICATIONはShellExecuteを実行して目的のアプリケーションを開始し、次にUPDATEAPPLICATIONを閉じます。

これはかなり基本的なプロセスであり、長年にわたってうまく機能してきました。UPDATEAPPLICATIONプログラムはDelphiアプリケーションであり、Delphi、VB6、MS Access、.NETなどの他のアプリケーションが混在しています。

【問題点】VistaやWindows7への移行に伴い、セキュリティが飛躍的に変化しました。UPDATEAPPLICATION UACの性質上、管理者アクセスまたはUACを完全にオフにしないと、アプリケーションを実行できません。現在、多くのアプリケーションを.NETにアップグレード中です。このプロセス中、アプリケーションとUPDATEAPPLICATIONをUACに準拠させたいと思います。私が調査したことから、これを行う唯一の方法は、WindowsサービスとしてUPDATEAPPLICATIONを作成することです。したがって、基本的に、UPDATEAPPLICATIONの機能をWindowsサービスアーキテクチャに複製する必要があります。

[私のデザイン]DelphiXE2を使用しています。私の設計は、単一のソリューションを形成する3つの部分で構成されます。Windowsサービス、Windowsサービスと対話するための小さなトレイアプリケーション、およびWindowsサービスにメッセージを送信する再設計されたアプリケーションです。

  1. 私のWindowsサービス(これをUPDATESERVICEと呼びます)はWindowsサービスとして実行され、要求をリッスンするTCPサーバーを作成します。
  2. トレイアプリケーション(これをTRAYAPPと呼びます)は、TCPクライアントを使用してUPDATESERVICEを構成/管理します。
  3. 私のUSERAPPLICATIONは、開始されると、「このアプリケーション」が開始されたことを示すTCPメッセージをUPDATESERVICEに送信します。

[UPDATESERVICE]メッセージをリッスンします。USERAPPLICATIONが開始したというメッセージを受信すると、Webサービスを呼び出して更新があるかどうかを確認します。存在する場合は、アプリケーションを閉じてUPDATESERVICEがアプリケーションを更新できるようにするようにユーザーに通知されます。UPDATESERVICEは適切なファイルをダウンロードし、アプリケーションを更新します。

私がやろうとしていることの基本を説明したので、私は答える必要がある私の特定の質問をすることができます。これらはすべて、Windowsサービスを構築する方法と関係があります。また、スレッド管理にOmniThreadを使用する予定です。

サービスが開始したら、TCPサーバーを作成する必要があります。

  1. TCPサービスはそれ自体のスレッドで作成する必要がありますか?
  2. TCPサービスがそれ自体のスレッドである場合、スレッドを存続させるにはどうすればよいですか?それ以外の場合は、TCPサービスを開始できますが、スレッドを実行し続けるためにTCPサービスユニット内でどのコードを使用するかわかりません。
  3. どのWindowsサービスイベントがTCPサービスを作成する必要がありますか?OnExecute?OnStart?OnCreate?結局のところ、どのイベントを使用すべきかが明確ではありません。
  4. TCPサービスが何かを行うためのメッセージを受信した場合、作業はTCPサービススレッド内で実行する必要がありますか、それともメインのUPDATESERVICEから生成された新しいスレッドで実行する必要がありますか?例えば:
    • TCPサービスがHTTPを使用して更新をチェックするメッセージを受け取った場合、TCPサービススレッドがこの作業を行うために新しいスレッドを生成する必要があります
    • または、TCPサービススレッドがUPDATESERVICEにメッセージを送信して、この作業を行うための新しいスレッドを生成する必要があります。
    • それも重要ですか?
  5. DelphiコードでWindowsサービスを開始/停止/登録/登録解除することは可能ですか?

これが私の質問です。これにはおそらく正しい/間違った答えはありませんが、単に経験に基づく好みです。Delphiを使用してサービスを構築したことがある場合は、おそらく私が役立つと思われる入力があります。基本的な「サービスを開始してスリープする」よりも堅牢なプロジェクトがあり、それを共有する用意がある場合は、実行していなくても、コードを疑似的に実行していなくても、これは非常に貴重です。長い間質問を読んでくれてありがとう。あなたがこれについて行くより良い方法を考えることができるならば、あなたの考えを共有してください。私たちのアプリケーションのいくつかは一般の人々がダウンロードして実行できるので、期待される環境を完全に制御することはできません。任意のアドバイス/コメント/ヘルプをいただければ幸いです。

4

1 に答える 1

30

速い答え:

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.
于 2012-05-10T16:10:27.567 に答える