デベロッパー

デベロッパー

Windows サービスを柔軟に管理するサービスマネージャの製作:パート1

J. Ambrose Little
2005年12月6日 / 11:00
 
 

はじめに

 この記事は2部構成の前半です。パート1では、.NET Frameworkにおける「サービス」の起動方法や使い方、プラグアンドプレイサービスの追加方法などについて詳しく説明しますパート2はサービスの仕組みの詳細です。.NET Remotingやリフレクションを使用したアプリケーションドメインのローディングとアンローディングなど、高度なトピックを取り上げます。

 こんなことを考えたのは私が最初ではなく、また最後でもないでしょう。事実、リソースセクションには同様の目的で書かれた記事が2つあり、私もそれを参考にしました。しかし、使いやすさと柔軟性では、僭越ながら私の実装がそれらに勝ると思います。

 まず、用語について一言触れておきましょう。私たちはよくWindowsサービスとか、Webサービス、サービス指向アーキテクチャなどと言いますが、ここで使われる「サービス(service)」という単語は、これらの概念の本当の意味を表すのに必ずしも適切な単語ではありません。英語には代わりに使用できる単語があまりないのですが、ITの世界で手垢にまみれていない代替語を探すと、奉仕とか世話を意味する「ミニストレーション(ministration)」あたりが最適でしょうか。私自身は、難しげに聞こえる言葉を使いたがる性格ですが、ここは従来の業界用語を踏襲し、特に概念を明確化する必要があるときは、適切な修飾語の助けを借りることにします。

 ここで紹介しようとしている新概念は、今回取り上げるWindowsサービスの名前「.NET Service Manager」と関係があります。このWindowsサービスは、平たく言えば、本来なら独立したWindowsサービスとしてインストールすべきサービスを、ドラッグアンドドロップやXCOPYでインストールするサービスです。基本的には、ユーザーがログインしていない間も実行しておきたい長時間稼動のプロセスや、時間限定のプロセスなどが、Windowsサービスの候補となります。

 残念ながら、.NETでもWindowsサービスの実装は簡単ではありません。Windowsサービスのインストールや開始/停止は[サービス]MMCスナップインで手動で行わなければなりません。これは非常に面倒な作業であり、Windowsサービスがいくつもあると、サービスリストが手に負えなくなります。

 そこで、今回の.NET Service Managerの出番となります。このアプリケーションでは、Windowsサービスを書き、インストールし、(MMCスナップインを使って)開始する、という作業が一度だけで済みます。その後は、ASP.NETなどと同等(またはそれ以上)のXCOPYを使用して実際の処理を行えるようになります。アセンブリ属性を適用し、簡単なインターフェイスを実装するだけで、どのような.NETコードライブラリ(DLL)アセンブリでも.NET Service Managerの下で実行できます。このようなアセンブリを、私は「被管理サービス」と呼んでいます。「Windowsサービスのように動作するものの、実は.NET Service Managerの管理下にあるサービス」の意味です。

 この記事(パート1、パート2とも)で「サービス」に言及するときは、修飾語を付け、Windowsサービス(従来の「NTサービス」)なのか、被管理サービス(上記パラグラフで説明したサービス)なのかを示すことにします。パート1では、被管理サービスの実装方法について詳しく説明します。この説明に従えば、.NET Service Managerの働きの仕組みを知らなくても、.NET Service Managerとその機能を利用することができます。働きの仕組みについては、パート2で説明します。

 .NET Service Managerを使えば、サービスをプロセスディレクトリにコピーするだけでインストールと開始ができます。また、サービスの更新も同じ配備メカニズムで簡単に行うことができ、その際に.NET Service Manager自体を停止・開始する必要はありません。すべての被管理サービスは独自のアプリケーションドメインで動作しているので、あるサービスを更新しても、他のサービスには何の影響もありませんし、.NET Service Manager自体を停止させる必要もありません。同様に、ある被管理サービスを停止させたいときは、そのDLLをプロセスディレクトリから取り除く(削除もしくは移動する)だけで済み、インストール同様、アンインストールも簡単にできます。

 .NETがコードライブラリのconfigファイルをサポートしていないことはご存じでしょう。それにはちゃんとした理由がありますが、私自身は「その機能があればすごく便利なのに」と思うケースを何度も経験しています。被管理サービスもそうしたケースの1つです。そこで、設定値を「app.config」のようなファイル(たとえば、「SampleService.dll.config」)に格納するための簡単なインターフェイスを作ってみました。このconfigファイルは、アプリケーション設定値を持つ典型的なconfigファイルと同じ規則に従っています。この技術の実装例も後で紹介します。

 あるコードを被管理サービスとして動作させるために必要な手順は、次の2つだけです。

  1. 1つの型にServiceBroker.IServiceインターフェイスを実装します。
  2. アセンブリにServiceBroker.ServiceEntryPointAttributeを適用します。

 これを使用して、サービスの表示名と、サービスエントリポイントとなる型(ステップ1でインターフェイスを実装した型)を指定します。

 もう1つ、configファイルの機能を利用するためのステップ3を加えることもできますが、これが必要なのは、その被管理サービスが変更可能な設定値を必要としている場合だけです。この3つのステップのそれぞれについて、以下で説明していきます。

 もちろん、「ServiceBroker.dll」アセンブリへの参照も必要ですが、これは.NET Service Managerとともにインストールされるので、インストール先ディレクトリ(たとえば、「C:Program FilesLittlechip.NET Service Manager」)で参照すればよいでしょう。あるいは、記事「Where’s My Assembly?」のように共通のアセンブリ参照ディレクトリにコピーして、そこで参照することもできます。

ServiceBroker.IServiceを実装する

 ServiceBrokerは.NET Service Managerに付随するアセンブリで、.NET Service Managerのエンジンとして働くと同時に、.NET Service Managerと被管理サービスとの対話に必要な型を提供します。IServiceインターフェイスの詳細をリスト1に示します。

リスト1 「IService.cs」
using System;
namespace ServiceBroker
{
    ///<summary>
    /// Interface that all services using the
    /// ServiceBroker must implement on the type specified
    /// by the <see cref=
    /// "ServiceEntryPointAttribute.EntryPointTypeName"/>.
    ///</summary>
    public interface IService
    {
        ///<summary>
        /// Starts the service functionality.
        ///</summary>
        void StartService();
        ///<summary>
        /// Stops the service functionality.
        ///</summary>
        void StopService();
    }
}

 ご覧のとおり、被管理サービスのインターフェイスは実に簡単で、開始メソッドと停止メソッドが必要とされるだけです。これは、もちろん、[サービス]MMCスナップインで使用できる典型的な操作に対応しています。Windowsサービスの再開という操作では、StopServiceメソッドが呼ばれ、次いでStartServiceメソッドが呼ばれるだけですから、メソッドとしては2つあれば足ります。

 例として、きわめて単純な被管理サービスを実装してみました。これは実装方法を示すためだけの実装であり、名前はSampleServiceです。このアセンブリ中でIServiceインターフェイスを実装している型を、リスト2に示します。

リスト2 「Test.cs」
using System;
namespace SampleService
{
    ///<summary>
    /// Summary description for Test.
    ///</summary>
    public class Test : ServiceBroker.IService
    {
        #region IService Members
        public void StartService()
        {
            ServiceBroker.Logger.WriteToLog(AppSettings["StartText"],
                System.Diagnostics.EventLogEntryType.Information);
        }
        public void StopService()
        {
            ServiceBroker.Logger.WriteToLog(AppSettings["StopText"],
                System.Diagnostics.EventLogEntryType.Information);
        }
        #endregion
    }
}

 AppSettingsメンバを参照していることにお気づきでしょう。ここでは、リストを簡単にするためにこのメンバを省いていますが、後であらためて取り上げます。ここで重要なのは、IServiceインターフェイスを実装していることと、ごく基本的な機能(イベントログへのメッセージ書き出し)を各メソッドに用意していることです。

 もちろん、サービスを実装するのであれば、System.Threading.Timerインスタンスをいくつか使い、何らかの活動が定期的に行われるようにしたり、System.IO.FileSystemWatcherを実装して、ファイルシステム活動を監視したりすることが必要になるでしょう。この記事を読んでいるような読者なら、当然、必要なWindowsサービス機能について具体的な考えをお持ちでしょうから、私の方から何をすべきだとは申し上げません。例を見ていただければ、何をどうすればよいかがわかるはずです。

属性を適用する

 もちろん、属性を適用せず、リフレクションで済ませる方法もあります。アセンブリ中の型を調べ、ServiceBroker.IServiceインターフェイスを実装している最初の型を探してもよかったでしょう。ただ、そのような型探しの方法には、時間がかかる危険があります。また、私としては、被管理サービスに親しみやすい表示名を指定できるようにしたいという思いがあって、カスタム属性を使うことにしました。当該サービスに関するログ項目を書き込むときには、その表示名が使われます。

 アセンブリに対するカスタム属性の適用はきわめて簡単です。Visual Studioを使っている場合、コードライブラリプロジェクトでは既定で「Assembly.cs」(VBなら「Assembly.vb」)ファイルが生成されます。そこで、アセンブリレベル属性を指定することが求められます。実際にアセンブリレベル属性を指定する場所はどのコードファイルでもかまいませんが、usingステートメントより後ろ、型や名前空間の宣言より前であることが必要です。このサンプルでは、「Assembly.cs」ファイルを使用しています。

リスト3 「Assembly.cs」
[assembly: ServiceBroker.ServiceEntryPoint("SampleService",
 "SampleService.Test")]
[assembly: System.Reflection.AssemblyVersion("1.0.*")]

 生成される「Assembly.cs」ファイルには、普通、大量のコメントと少量の標準アセンブリレベル属性(タイトル、内容記述、会社、著作権など)が含まれています。私は、usingステートメント(VBではImportsステートメント)を含むすべてを取り除き、AssemblyVersionAttributeだけを残しています。何を設定しなくても、アセンブリバージョンだけは設定するべきだと思います。

 また、既にお気づきのことと思いますが、私はServiceBrokerアセンブリからのカスタム属性を適用しています。この属性のコンストラクタは引数を2つとります。1つは被管理サービスの表示名、もう1つは、サービスの開始・停止の際に.NET Service Managerに使用させる型の完全型名です。2つ目の引数に指定する型が、IServiceインターフェイスを実装する型(たとえば、リスト2Test型)でなければならないのは明らかです。ここでは、SampleService.Test型を使用するよう指定しています。

 変更可能なアプリケーション設定値が特に必要ない場合は、ここで作業は終わりです。これをコンパイルして、.NET Service Managerのプロセスディレクトリ(つまり、.exeが置かれているディレクトリ)にドロップします。後は.NET Service Managerを実行すれば、それがアセンブリを自動的にロードし、サービスエントリポイント型を見つけてインスタンスを作成し、その型のIService.StartServiceメソッド実装を呼んでくれます。

 被管理サービスを更新するときは、必要な変更を施し、コンパイルした新バージョンを同じディレクトリにドロップします。.NET Service Managerは、稼動中に新バージョンを検出すると、旧バージョンを停止してアンロードし、新バージョンをロードして開始します。同様に、DLLファイルをディレクトリから移動または削除しても、.NET Service Managerにそれを停止してアンロードさせることができます。

DLLのconfigファイルを使用して柔軟性を強化する

 ServiceBrokerのConfigクラスのおかげで、被管理サービスに簡単に.configファイルを追加できます。残念ながら、Windowsフォーム/サービスアプリケーションの場合と異なり、「app.config」ファイルを用いて「<assemblyName>.dll.config」ファイルを生成することはできません(Visual Studioがそのようにセットアップされていません)。そのため、configファイルのコピーという多少余分な作業が必要です。つまり、アセンブリより先(または同時)にconfigファイルをコピーし、.NET Service Managerがアセンブリをロードしたときにconfigファイルが使えるようにしておきます。

 いずれにせよ、configファイルのフォーマットは標準の「app.config」ファイルと同じです。ただし、ServiceBrokerのconfigハンドラが認識するのはアプリケーション設定値だけです。SampleServiceアセンブリで使用するconfigファイルをリスト4に示します。

リスト4 「SampleService.dll.config」
<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
    <appSettings>
        <add key="StartText"
         value="This is where you would implement logic
 to start your service."/>
        <add key="StopText"
         value="This is where you would implement logic
 to stop your service."/>
    </appSettings>
</configuration>

 configファイルを使用するには、Testクラスに数行分のコード(リスト2では除いていた数行)を追加するだけです。リスト5に、configファイル関連のコードを含めた完全なTestクラスを示します。

リスト5 完全な「Test.cs」
using System;
namespace SampleService
{
    ///<summary>
    /// Summary description for Test.
    ///</summary>
    public class Test : ServiceBroker.IService
    {
        internal static ServiceBroker.Config AppSettings = 
            new ServiceBroker.Config();
        #region IService Members
        public void StartService()
        {
            ServiceBroker.Logger.WriteToLog(AppSettings["StartText"],
                System.Diagnostics.EventLogEntryType.Information);
        }
        public void StopService()
        {
            ServiceBroker.Logger.WriteToLog(AppSettings["StopText"],
                System.Diagnostics.EventLogEntryType.Information);
        }
        #endregion
    }
}

 結局、追加するのは、ServiceBroker.Configstatic(VBではShared)な識別子を宣言するための1行です。この識別子はアセンブリ内部の型に見えればいいだけなので、internal(VBではFriend)と宣言します。これで、Test.AppSettingsフィールドを用いて、コード中のアプリケーション設定値へのアクセスを開始できます。このフィールドには、System.Configuration.ConfigurationSettings.AppSettingsによく似た文字列インデクサがあるので、使い方は標準のアプリケーション設定値のアクセスに非常によく似ています。実は、ConfigurationSettingsを作成し、AppSettingsメンバをそこに置いて、いっそう似せることもできますが、あまり似せるのも考えものです。というのは、System.Configuration名前空間に同じクラスがある場合、その名前空間へのusingステートメント(VBではImportsステートメント)があると、クラスの衝突が起こりやすいからです。

 さあ、終わりました。これで、XCOPYで配備でき、ホット更新が可能で、簡単に設定できるWindowsサービスが得られました。他の「app.config」ファイルとは違い、ServiceBrokerはユニークです。configファイルを監視して変更の有無を察知し、configファイルが更新されたときは、キャッシュに記憶されている設定値(アプリケーションの実行中、高速アクセスのためにメモリにロードされています)を更新できます。しかも被管理サービスを再開する必要はありません。

 .NET Service Managerのインストーラは、.NET Service ManagerをLocal Systemアカウントの下で実行されるものとしてインストールします。しかし、事情が許せば、これをもっと権限の小さなアカウントに変更することをお勧めします。実は、すべての被管理サービスが同じWindowsサービスのパーミッションの下で実行されることが、.NET Service Managerの弱点でもあります。もっときめ細かな管理が必要なときは、さらに別のWindowsサービスを開発しなければなりません。

 ダウンロードサンプルに含まれているインストーラを実行し、.NET Service Manager Windowsサービスを開始してください。これでいつでも被管理サービスの作成と配備ができます。パート2では、なぜ.NET Service Managerの下でこれが可能になるのかを説明します。

参考資料

著者紹介

J. Ambrose Little(J. Ambrose Little)
ASPAlliance編集長。ASPInsiderであり、Microsoft ASP.NET MVPでもある。現在はWebアーキテクトとして、フロリダ州タンパにある大手信用組合に勤務。これまでに、Verizon社のコンサルタントとしてXML Webサービスと中間層コンポーネントを開発し、BOk Financial社のWebサービス部門で同社イントラネット用にASP.NETアプリケーションを開発。.NET以前にも数年のプログラミング経験があり、ASPとVB COM/DCOMを用いたWebアプリケーション開発に従事。
中世ヨーロッパ史の研究が趣味で、この分野で学士号を取得。ソフトウェア開発以外では、映画鑑賞、読書、執筆、弁証論、ビリヤード、フーズボール、チェス、バドミントンなどを楽しむが、最大の楽しみは、もちろん、妻Christianeに娘Bridgetと過ごすことである。
メールの宛先はambrose@aspalliance.com
【関連記事】
日本のEメールマーケティングはどのステージか?
ボーランド、マルチ言語対応の新 IDE 製品を
シングルインスタンスを Windows フォームにデータバインドする方法
テレビ局のホームページ、よく見るのは「フジテレビ」トップで7割強
Microsoft C#での HTML 構文解析

New Topics

Special Ad

ゆりかごからロケットまで、すべての乗り物をエンジョイ
ゆりかごからロケットまで、すべての乗り物をエンジョイ えん乗り」は、ゆりかごからロケットまで、すべての乗り物をエンジョイする、ニュース、コラム、動画などをお届けします! てんこ盛りをエンジョイするのは こちらから

Hot Topics

IT Job

Interviews / Specials

Popular

Access Ranking

Partner Sites