Delphiでのサービス・アプリケーション

このメモは, http://finn.mobilixnet.dk/delphi/service/service.htm にあった英文解説書の翻訳(ともいえぬが)である。

プロジェクトの新規作成

メニューから,ファイル/新規作成/その他を実行。新規作成ダイアログからサービス アプリケーションを選択してOKをクリック。

サービスのインストール・アンインストール

 ServiceApp.exe /install
 ServiceApp.exe /uninstall

プロンプトを表示させないようにするには/silentスイッチもつける。
サービスが開始状態のままだと再コンパイルなどはできないので,修正するときはサービスを止める。アンインストールまではする必要はない。ただし,Nameプロパティ,DisplayNameプロパティを変更するときはアンインストールする必要がある。

サービスのコード記述

基本的にはサービスのコードを記述する場所は2カ所ある。OnExecuteまたはOnStartイベントである。

OnExecuteイベント

TService.OnExecuteメソッドにコードを書く。必要ならスレッドを生成するコードを書くことも可能。
ヘルプでOnExecuteを引くと,

OnExecute イベントは,サービスに関連付けられたスレッドが起動したときに発生します。

 Occurs when the thread associated with the service starts up.~

新しいスレッドを生成して,OnStart イベントハンドラで個々のサービス要求を処理しているのではない場合,OnExecute イベントがサービスを実現する場所です。OnExecute イベントハンドラが完了すると,サービススレッドは終了します。ほとんどの OnExecute イベントハンドラは,サービススレッドの ProcessRequests メソッドを呼び出すループを保持しているので,他のサービス要求はロックアウトされません。

 If you are not spawning a new thread to handle individual service requests in
 an OnStart event handler, this is where you implement the service. When the 
 OnExecute event handler finishes, the service thread terminates. Most OnExecute
 event handlers contain a loop that calls the service thread’s ProcessRequests
 method so that other service requests are not locked out.

OnStartイベント

実行するべきコードを書いたスレッド(TThread)を作って,このOnStartでスレッドをスタートさせる。
ヘルプでOnExecuteを引くと,
OnStartup イベントは,OnExecute イベントの前にサービスが初めて起動するときに発
生します。

 OnStartup occurs when the service first starts up, before the OnExecute event.~

このイベントは,サービスを初期化するために使用します。たとえば,個別のスレッドで各サービス要求を処理する場合(要求の処理に時間がかかる場合 によい方法である),OnStart イベントハンドラで要求に対するスレッドが生成されます。サービスを起動した場合,Started を True に設定します。

 This event should be used to initialize the service. For example, if each 
 service request is handled in a separate thread (a good idea if handling the 
 request takes much time) the thread for a request is spawned in an OnStart 
 event handler. 

どちらのメソッドを使うかはあなた次第。両方ともちゃんと動く。
次は双方のメソッドのサンプル

OnExecuteイベントを使う

 procedure TCompanySqlDatabaseSpecialSomething.ServiceExecute(
   Sender: TService);
 const
   SecBetweenRuns = 10;
 var
   Count: Integer;
 begin
   Count := 0;
   while not Terminated do
   begin
     Inc(Count);
     if Count >= SecBetweenRuns then
     begin
       Count := 0;
       { place your service code here }
       { this is where the action happens }
       SomeProcedureInAnotherUnit;
     end;
     Sleep(1000);
     ServiceThread.ProcessRequests(False);
   end;
 end;

シャットダウンとかコンパネのサービスからサービスが停止されるまで,ループを回す。この例では SomeProcedureInAnotherUnitは10秒ごとにコールされます。10秒待つのにSleep(10000)を使用しないのに注意する こと。そのやり方だとSCM(Service Control Manager)から送られたコマンドに素早く反応することができない。ここでは,その代わりに1秒だけスリープさせて SomeProcedureInAnotherUnitを最後にコールしてから何秒たったかをカウントしている。
OnExecuteイベントのかわりに,何らかの初期化をするならOnStartを使ってもよい。そうすると,初期化中になにか必要な設定がかけているのがてサービスをスタートしたくないときにStarted変数をfalseにセットすることができる。

OnExecuteイベントを使用するやり方の利点・欠点

利点
コードがシンプル。二次的なスレッドを生成する必要がない。
サービスの中断・再開は余分なコードなしで自動的にハンドリングされる。
欠点
SomeProcedureInAnotherUnit手続きは毎回非常に短い時間で終了しなければならない。
コードの実行に長い時間がかかる場合は,かわりに二次的なスレッドをOnStartイベントで生成することを考えるべきである。

OnStartイベントを使用する

はじめに,サービスの行う全てのコードを配置する二次的スレッドのクラスを定義する必要がある。
スレッドを作成するとき,通常はスレッドクラスを作る。ファイル/新規作成/その他からスレッドオブジェクトを選択して作成できる。
スレッドに関する経験があまりない場合は,サービス・アプリケーションについて続ける前に,スレッドに関する実用的な知識を得る必要がある。
Delphiでのスレッドプログラミングに関するチュートリアル
http://www.pergolesi.demon.co.uk/prog/threads/ToC.html
http://www.sklobovsky.com/community/index.html
http://sklobovsky.nstemp.com/community/threadmare/threadmare.htm
http://sklobovsky.nstemp.com/community/threadmare/perks.htm
http://sklobovsky.nstemp.com/community/threadmare/fixes.htm
TThread物でどう例外を扱うか--Borland Developer Support Staff:
http://community.borland.com/article/0,1410,10452,00.html
TMyServiceThread と呼ぶTThreadを作る方法を言おう。このスレッドはExecuteメソッドが終わると自動的にそれ自体を解放するように作られていなければならな い。これは簡単で,単にFreeOnTerminateプロパティをTrueにするだけ。
TServiceのPrivateセクションにスレッド変数を次のように定義する。

   private
     { Private declarations }
     MyServiceThread: TMyServiceThread;

OnStartとOnStopイベントに次のように記述する

 procedure TCompanySqlDatabaseSpecialSomething.ServiceStart(
   Sender: TService; var Started: Boolean);
 begin
   { Create an instance of the secondary thread where your service code is placed }
   MyServiceThread := TMyServiceThread.Create;
   { Set misc. properties you need (if any) in your thread }
   //MyServiceThread.Property1 := whatever;
   // and so on
   MyServiceThread.Resume;
 end;
 
 procedure TCompanySqlDatabaseSpecialSomething.ServiceStop(Sender: TService;
   var Stopped: Boolean);
 begin
   MyServiceThread.Terminate;
 end;

二次的なスレッドがOnStartで生成されて開始される。スレッドは止まれという通知を受け取るまで走り続ける。スレッドはサービスのOnStopイベントでスレッドのTerminateメソッドをコールすることで停止する。
Terminate メソッドをコールすることがそのスレッドを停止することとは限らないことに注意すること。Terminateメソッドをコールすること は,TerminatedプロパティをTrueにセットするだけで,スレッドが停止すべきかを見るために短い間隔でこれをチェックするのはスレッド次第で ある。そして,スレッドは単にスレッドのExecuteメソッドを出る時に停止する。
スレッドの非常に簡単な例

 interface
 
 uses
   Windows, Messages, SysUtils, Classes, Graphics;
 
 type
   TMyServiceThread = class(TThread)
   private
     { Private declarations }
   protected
     procedure Execute; override;
   public
     constructor Create;
   end;
 
 implementation
 
 { Important: Methods and properties of objects in visual components can only be
   used in a method called using Synchronize, for example,
 
       Synchronize(UpdateCaption);
 
   and UpdateCaption could look like,
 
     procedure TMyServiceThread.UpdateCaption;
     begin
       Form1.Caption := 'Updated in a thread';
     end; }
 
 { TMyServiceThread }
 
 constructor TMyServiceThread.Create;
 // Create the thread Suspended so that properties can be set before resuming the thread.
 begin
   FreeOnTerminate := True;
   inherited Create(True);
 end;
 
 procedure TMyServiceThread.Execute;
 const
   SecBetweenRuns = 10;
 var
   Count: Integer;
 begin
   { Place thread code here }
   while not Terminated do  // loop around until we should stop
   begin
     Inc(Count);
     if Count >= SecBetweenRuns then
     begin
       Count := 0;
 
       { place your service code here }
       { this is where the action happens }
       SomeProcedureInAnotherUnit;
 
     end;
     Sleep(1000);
   end;
 end;
 
 end.

OnStartメソッドを使って二次的スレッドを作る方法の利点・欠点

利点
コードは少し複雑でスレッドに関する知識が必要。(利点か?)
Count変数やそれにまつわるコードを削除して単にSleepをより大きな値で呼び出すことでExecuteメソッドをより簡単にできる。ただやたら大きな値でSleepしちゃだめだよ。
SomeProcedureInAnotherUnitプロシージャは毎回より長い時間を使うことができる。より長いインターバルをSleepできる。
しかし,WindowsのシャットダウンやSCMから停止命令が来たときに,妥当な時間内にスレッドとサービスを停止できるように適切な間隔でTerminatedプロパティをチェックするべきであることに注意。
欠点
サービスの中断や再開は自動的にハンドリングされない。OnPauseやOnContinueイベントを定義して中断・再開された時にスレッドに通知しなければならない。
単にスレッドのPause,Resumeメソッドを単純にコールするというやりかたは,やばいかもしれないので注意すること。
スレッドがファイルやデータベース接続やネットワーク接続をオープンしている場合,これらのリソースはスレッドが中断している間保持される。そうならないように,再開するか終了するまで,動作を停止して単にループするようにするプロパティをセットするべきである。
この方法でサービスの停止と再開をハンドリングする簡単な方法は,AllowPauseプロパティにFalseをセットして,これらのオプションを使えなくしてしまうことだ。
OnStartメソッドは,コードの実行に長い時間がかかる場合に有効である。
警告
意識するべきいくつかの問題がある。
まず,私は告白するべきことがある。
先に,サービス・アプリケーションがシャットダウンする時に自分自身を解放できるようにスレッドのFreeOnTerminateプロパティをTrueにセットするべきであると言った。これは大きな間違いだ。
実際に,サービス・アプリケーション内のスレッドに自身を終了させるのは,災害のレシピ(recipe for disaster)だ。
TServiceを終了する前にTServiceの中でスレッドが終了するのを確実にしなければならない。もしスレッドが終了する前にTServiceが終わったら,そのスレッドは途中で何をやっていたとしても単にKillされるだけである。これはもちろんよくない。
だから,TServiceの中でFreeOnTerminateプロパティをFalseに設定して,WaitForメソッドを使ってスレッドが終了するま待たなければならない。
別の落とし穴
サー ビスが手動で止められたときOnStopイベントが呼ばれる(OnShutdownは呼ばれない)。システムがシャットダウンする ときOnShutdownが呼ばれる(OnStopは呼ばれない)。したがって,ちゃんと終了処理をするためにはOnStopとOnShutdownの両 方をインプリメントしなければならない。普通は両方から共通のプロシージャをコールするのがベスト。

で,次のように

 type
   TCompanySqlDatabaseSpecialSomething = class(TService)
     procedure ServiceStart(Sender: TService; var Started: Boolean);
     procedure ServiceStop(Sender: TService; var Stopped: Boolean);
     procedure ServiceShutdown(Sender: TService);
   private
     { Private declarations }
     MyServiceThread: TMyServiceThread;
     procedure ServiceStopShutdown;
   public
     function GetServiceController: TServiceController; override;
     { Public declarations }
   end;
 
 procedure TCompanySqlDatabaseSpecialSomething.ServiceStart(Sender: TService;
   var Started: Boolean);
 begin
   // Allocate resources here that you need when the service is running
   { Create an instance of the secondary thread where your service code is placed }
   MyServiceThread := TMyServiceThread.Create;
   { Set misc. properties you need (if any) in your thread }
   //MyServiceThread.Property1 := whatever;
   // and so on
   MyServiceThread.Resume;
 end;
 
 procedure TCompanySqlDatabaseSpecialSomething.ServiceStop(Sender: TService;
   var Stopped: Boolean);
 begin
   ServiceStopShutdown;
 end;
 
 procedure TCompanySqlDatabaseSpecialSomething.ServiceShutdown(
   Sender: TService);
 begin
   ServiceStopShutdown;
 end;
 
 procedure TCompanySqlDatabaseSpecialSomething.ServiceStopShutdown;
 begin
   // Deallocate resources here
   if Assigned(MyServiceThread) then
   begin
     // The TService must WaitFor the thread to finish (and free it)
     // otherwise the thread is simply killed when the TService ends.
     MyServiceThread.Terminate;
     MyServiceThread.WaitFor;
     FreeAndNil(MyServiceThread);
   end;
 end;

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。