この記事は、Muminjon氏のブログの抄訳です
Windowsのソフトウェアを開発している場合、アプリケーションを24時間連続で稼働、あるいはコンピュータの電源が入っている間、アプリケーションを稼働させるニーズがあります。通常、その対象となるコンピュータは、ネットワークサーバーやデスクトップマシンの監視アプリケーションです。このような場合、最小限の対話を行うか、完全にサイレントなコンソール アプリケーションを作成することを考えるかもしれませんが、これよりも優れたソリューションがあります。
単純なコンソールや最小化された通常のGUIアプリは、Windowsのセッション終了、再起動、ユーザー権限などの問題に直面する可能性があります。この問題を解決する方法は、Windowsサービスを開発することです。このブログでは、Delphiを使用してWindowsサービスを構築するチュートリアルを解説いたします。
Table of Contents
なぜWindowsサービスを作成する必要があるのでしょうか?
長時間 バックグラウンドで実行するWindows サービスを作成する理由は複数あります。
- CPUに負荷のかかるデータの処理
- バックグラウンドでの作業項目のキューイング
- スケジュールに従って時間ベースの操作を実行
- ヘルパーアプリやユーティリティアプリの機能を邪魔しない操作性
通常、バックグラウンドサービスの処理にはユーザーインターフェイス (UI) は含まれませんが、UI はそれらを中心に構築できます。
Windowsサービスを開発するための前提条件とは?
サービスアプリの開発を始める前に、Delphi IDEの最新バージョンをチェックすることをお勧めします。多くの機能強化や新機能により、開発プロセスがさらにスムーズになります。さらに最近リリースされたばかりのDelphi 11.3 Community Editionを使用して、Delphiのプログラミング言語とその構文に慣れることができます。
Windowsサービスアプリケーションは、どのように動作するのか?
サービスアプリケーションは、クライアントアプリケーションからリクエストを受け、そのリクエストを処理し、クライアントアプリケーションに情報を返します。Web、FTP、電子メールサーバーは、サービスアプリケーションの一例です。
Windowsサービスアプリケーションは、ユーザーがログインしなくても実行できるWindowsアプリケーションです。Windows サービス アプリケーションがデスクトップと対話することはほとんどありません。このブログでは、Delphiを使用してWindowsサービスアプリケーションを作成するチュートリアルを紹介いたします。
サービスアプリケーションの実行時には、仮想の「サービスユーザー」として、またはシステムにアクセス権を持つ実際の一般ユーザーとして、デフォルトのユーザー権限セットを使用するように設定できます。これらのユーザー権限は、サービスアプリケーションがアクセス権を持つフォルダや「マッピングされた」ネットワークフォルダに影響を与える可能性があることを理解しておくことが重要です。可能であれば、常に完全なUNCパス名を使用するか、WindowsシステムコールまたはDelphiのランタイムTPathタイプの関数を使用して、%APPDATA%フォルダの位置や「マイドキュメント」タイプの仮想パスなどの特殊なフォルダの正しい位置を取得する必要があります。
Windowsのサービスアプリケーションは、多くの機能を提供することができます。サービス・ユーティリティ・ツールを起動したときに表示されるリストをご覧ください。これらのサービスは、使用する主要なGUIアプリケーションの中核となる場合があります。
Windowsサービスアプリとして作成する必要があるのはどんな時ですか?
少し前のことですが、筆者はファイルサーバーのディスクの空き容量を監視するためのシステム監視ユーティリティが必要でした。そこで筆者は1分ごとにチェックし、その情報をログファイルに書き込むユーティリティを作成しました。しかし、このユーティリティはユーザーがログオンしている必要があり、ユーザーがログアウトすると、アプリケーションも一緒に終了してしまいました。そのため解決策として、このアプリケーションをWindowsのサービスとして再構築し、コンピュータの電源が入っている間は、たとえユーザーがログインしていなくても、ずっと起動したままにできるようになりました。
Delphi や C++ Builderを使用したRAD Studio は、一般的なユーザー向けの対話型アプリケーションに最適化されていますが、サービスアプリケーションを簡単に作成することは十分可能です。
DelphiでWindowsサービスプロジェクトを作成するには?
Delphiで新しいプロジェクトを作成するには、Delphiの開発環境がコンピュータにインストールされている必要があります。Delphi を起動して実行したら、まず新しいプロジェクトを作成します。
Delphi を使用して新しいWindowsサービスプロジェクトを作成するには、以下の手順を実行します。
- IDEメニューの「ファイル」をクリックし、「新規作成」→「その他」を選択すると「新規項目」ダイアログボックスが表示されます。
- ダイアログボックスの左側から、Delphiプロジェクトを選択し、[Windows]カテゴリからWindowsサービスのプロジェクト形式を選択します。
- [OK]をクリックすると、新しいプロジェクトが作成されます。
なお、C++ Builderを使用してサービスプロジェクトを作成する場合も同様の手順となります。
新しいプロジェクトを作成した後、プロジェクトのプロパティとオプションを設定する必要があります。これを行うには、”プロジェクトマネージャ “ウィンドウでプロジェクト名を右クリックし、”オプション “を選択します。これにより、”プロジェクトオプション “ダイアログボックスが表示されます。このダイアログボックスでは、ターゲットプラットフォーム、出力ディレクトリ、コンパイラオプションなど、プロジェクトに関するさまざまなオプションを設定することができます。サービスの開発を進める前に、プロジェクトの要件に従ってこれらのオプションを設定することを確認してください。
Delphiを使用してWindowsサービスアプリを実装するには?
Delphiで新しいWindowsサービスプロジェクトを作成し、プロジェクトのプロパティとオプションを設定した後、次のステップは、サービス自体を実装することで、デフォルトで「Unit1.pas」と名付けられたメインとなるサービスユニットにコードを追加していきます。
まず、「プロジェクトマネージャ」ウィンドウの「Unit1.pas」ファイルをダブルクリックし、コードエディタで開いてください。これにより、Delphiサービスアプリケーションのスケルトンコードを含む、ユニットのコードファイルが表示されます。
ユニットに独自のコードを追加するには、さまざまなサービスイベントを処理することによって、サービスの動作を定義する必要があります。これらのイベントには、start、stop、pauseイベントがあり、それぞれサービスが開始、停止、一時停止されたときにトリガーされます。
これらのイベントを処理するために、Delphiの組み込みサービスコンポーネントである「TService」クラスを使用することができます。このコンポーネントには、「Start」「Stop」メソッドなど、サービスの動作を制御するために使用できるさまざまなメソッドやプロパティが用意されています。
なお、Delphi の組み込みサービス コンポーネントである「TService」クラスを使用できます。 このコンポーネントは、「Start」メソッドや「Stop」メソッドなど、サービスの動作を制御するために使用できるさまざまなメソッドとプロパティを提供します。
例えば「OnStart」イベントハンドラを使用して、サービス開始時に実行されるコードを定義することができます。そのためには、「Unit1.pas」ファイルに次のコードを追加します。
1 2 3 4 5 6 7 |
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean); begin //OnStartup は,OnExecute イベントの前にサービスが初めて起動するときに発生します。 //このイベントは,サービスを初期化するために使用します。 //たとえば,個別のスレッドで各サービスリクエストを処理する場合、 //(リクエストの処理に時間がかかる場合によい方法である)OnStart イベントハンドラでリクエストに対するスレッドが生成されます。 end; |
同様に「OnStop 」イベントハンドラを使用して、サービスが停止したときに実行されるべきコードを定義することができます。「Unit1.pas」ファイルに以下のコードを追加します。
1 2 3 4 5 |
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean); begin //SCM(Service Control Manager)がサービスを停止すると発生します。 //OnStop イベントハンドラを記述すると,サービスが停止したときに特定のアクションを実行できます。 end; |
start、stopイベントに加えて、サービスが一時停止したときに発生するpauseイベントも処理する必要がある場合があります。これを行うには、「OnStart」「OnStop」イベントハンドラと同様に「OnPause」イベントハンドラを使用することができます。
例えば、「Unit1.pas 」ファイルに以下のコードを追加することで、pauseイベントを処理することができます。
1 2 3 4 5 |
procedure TService1.ServicePause(Sender: TService; var Paused: Boolean); begin //SCM(Service Control Manager)が一時的にサービスを中断すると発生します。 //OnPause イベントハンドラを記述すると,サービスが一時的に中断したときに特定のアクションを実行できます。 end; |
これらの各イベントハンドラには、それぞれのイベントがトリガーされたときのサービスの動作を定義する独自のコードを追加することができます。例えば、「TService」コンポーネントの「Start」「Stop」メソッドを使用して、タイマーやスレッドを開始または停止したり、「Pause」メソッドを使用して、タスクの実行を一時停止したりすることができます。
メインサービスユニットにコードを追加し、サービスのイベントを処理したら、DelphiのデバッガとWindowsサービスマネージャを使用して、サービスのデバッグとテストを進めることができます。
Windowsサービスアプリケーションの機能をどのように実現するか?
ユニットのuses節にVcl.SvcMgrユニットを追加します。
TFormクラスの宣言を以下のように変更し、メインフォームの親クラスをTServiceに変更します。
1 2 |
type TService1 = class(TService) |
ここでソースコードの全文を紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
unit ServiceUnit; interface uses Winapi.Windows , Winapi.Messages , System.SysUtils , System.Classes , Vcl.Graphics , Vcl.Controls , Vcl.SvcMgr , Vcl.Dialogs , BackgroundThreadUnit , System.Win.Registry; type TService1 = class(TService) procedure ServiceExecute(Sender: TService); procedure ServiceStart(Sender: TService; var Started: Boolean); procedure ServiceStop(Sender: TService; var Stopped: Boolean); procedure ServicePause(Sender: TService; var Paused: Boolean); procedure ServiceContinue(Sender: TService; var Continued: Boolean); procedure ServiceAfterInstall(Sender: TService); private FBackgroundThread: TBackgroundThread; { Private declarations } public function GetServiceController: TServiceController; override; { Public declarations } end; {$R *.dfm} var MyService: TService1; implementation procedure ServiceController(CtrlCode: DWord); stdcall; begin MyService.Controller(CtrlCode); end; procedure TService1.ServiceExecute(Sender: TService); begin while not Terminated do begin ServiceThread.ProcessRequests(false); TThread.Sleep(1000); end; end; function TService1.GetServiceController: TServiceController; begin Result := ServiceController; end; procedure TService1.ServiceContinue(Sender: TService; var Continued: Boolean); begin FBackgroundThread.Continue; Continued := True; end; procedure TService1.ServiceAfterInstall(Sender: TService); var Reg: TRegistry; begin Reg := TRegistry.Create(KEY_READ or KEY_WRITE); try Reg.RootKey := HKEY_LOCAL_MACHINE; if Reg.OpenKey('SYSTEMCurrentControlSetServices' + name, false) then begin Reg.WriteString('Description', 'Blogs.Embarcadero.com'); Reg.CloseKey; end; finally Reg.Free; end; end; procedure TService1.ServicePause(Sender: TService; var Paused: Boolean); begin FBackgroundThread.Pause; Paused := True; end; procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean); begin FBackgroundThread.Terminate; FBackgroundThread.WaitFor; FreeAndNil(FBackgroundThread); Stopped := True; end; procedure TService1.ServiceStart(Sender: TService; var Started: Boolean); begin FBackgroundThread := TBackgroundThread.Create(True); FBackgroundThread.Start; Started := True; end; end. |
そしてバッググラウンドで動作するスレッドの実装は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
unit BackgroundThreadUnit; interface uses System.Classes; type TBackgroundThread = class(TThread) private FPaused: Boolean; // FTerminated: Boolean; // FOnTerminate: TNotifyEvent; protected procedure Execute; override; public procedure Pause; procedure Continue; // procedure Terminate; // property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate; end; implementation uses System.SysUtils, System.IOUtils; procedure TBackgroundThread.Continue; begin FPaused := False; end; // process something here procedure TBackgroundThread.Execute; var LogFile: TextFile; begin try FPaused := False; AssignFile(LogFile, 'C:TempLogs.log'); Rewrite(LogFile); while not Terminated do begin if not FPaused then begin WriteLn(LogFile, 'Logs From Background Thread: ' + DateTimeToStr(Now)); end; TThread.Sleep(1000); end; finally CloseFile(LogFile); end; end; procedure TBackgroundThread.Pause; begin FPaused := True; end; end. |
これで、サービスを実装することができます。そしてProjectsウィンドウで、コンテキストメニューを開き、プロジェクトをビルドしてください。
Windowsサービスマネージャを使用したサービスのテスト
Windows サービスマネージャを使用して、サービスをテストすることができます。このユーティリティでは、サービスの開始、停止、一時停止、再開を行い、その状態や発生したエラーを確認することができます。
サービスをインストールするには、以下の手順で行います。
- 管理者権限でコマンドプロンプトで開く(サービスを登録するためには、管理者権限が必要)
- Delphiのプロジェクトをビルドし、exeファイルが作成されたパスへ移動(例えば、Win32¥Debug)
- サービス名 “/install “コマンドで登録します。(例えば、ServiceDemoBlogsEmbarcdero.exe /install)
サービスをインストールしたら、Windowsサービスマネージャを使用して、サービスを開始、停止、一時停止、または再開することができます。Windowsコントロールパネルの管理ツールのメニュー内の「サービス」アプリを開きます。サービスの一覧で、さきほどインストールしたサービスを検索(ここでは、ServiceDemoBlogsEmbarcdero)し、その上で右クリックし、コンテキストメニューから必要なアクションを選択します。
数秒後、サービスマネージャーからサービスを停止し、Logsファイルを確認してください。(このチュートリアルでは、C:¥Tempフォルダ内にLogs.logファイルを出力しています)
このブログのチュートリアルで紹介いたしましたプロジェクトの全体のソースコードは、こちらのリポジトリから入手できます。
FAQ
Windowsサービスをデバッグする方法は?
Windowsサービスを削除する方法は?
管理者権限でコマンドプロンプトを起動し、 サービス名 “/uninstall “コマンドで解除できます。(例えば、ServiceDemoBlogsEmbarcdero.exe /uninstall)
Windowsのサービスアプリをサイレントインストールするには?
以下のようにコマンド ラインの最後に「/silent」を追加すると、メッセージボックスの表示はされません。
コマンド例: myserviceapp.exe /install /silent
64bitのWindows上で32bitのサービスを動作させることはできますか?
32ビットサービスは64ビット版Windowsで正常に動作します。ただし、ネットワークシステム管理者の中には、Windowsのグループポリシーオプションを設定し、64ビット版以外のサービスの実行を許可していない場合もありますので、事前に確認してください。
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition