SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

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

.NETを使用した拡張可能なWindowsサービス管理クラスの実装

  • X ポスト
  • このエントリーをはてなブックマークに追加

本稿では、Windowsサービスを書き、インストールし、開始する、という作業が一度だけで済むWindowsサービスマネージャの実装方法を解説します。2部構成のパート2では、このサービスの仕組みの詳細を見ていきます。.NET Remotingやリフレクションを使用したアプリケーションドメインのローディングとアンローディングなど、高度なトピックを取り上げます。

  • X ポスト
  • このエントリーをはてなブックマークに追加

はじめに

 パート1では、.NET Service Managerとはいったい何をするものか、そしてそれの下で動作する被管理サービスのインストールと設定はどうすればよいか、という点を説明しました。パート2では、.NET Service Managerの働きの仕組みを詳細に見ていきます。つまり、ドラッグアンドドロップ配備などの便利な働きがなぜ可能なのか、ということです。こうした機能を背後で支えている諸概念には、他の.NETプロジェクトにも応用できるものが少なくありません。

 .NET Service Managerは、いくつかの中核的技術の上に成り立っています。たとえば、.NET Remoting、AppDomain、Reflection、シャドウコピー、そしてクラスやインタフェースといった標準的なオブジェクト指向技術です。そのうち、アセンブリの動的ロードとアンロードでの中心となる技術がRemotingです。Remotingを利用すると、コードのロード、ホスト、実行を隔離されたメモリ領域(アプリケーションドメイン、もしくはAppDomainと呼び出されます)で行い、外見上は標準のWindowsプロセスとなんら変わりなく動作させることができます。

AppDomainとRemotingの関係

 AppDomainは、小型のプロセスのようなもので、信頼性と安全性の確保に必要な分離性を実現していますが、プロセッサやメモリのオーバーヘッドは完全なWin32プロセスよりも小さく抑えられています。1つの実プロセスの内部でいくつものAppDomainが実行されます。同じプロセス内にあるAppDomain間の通信は、当然、プロセス間通信より高速であると期待できますが、やはりRemoting境界を越えた往来が必要となるため、パフォーマンスには多少の影響が出ます。

 リモート通信に必要なオーバーヘッドを理解するために、1つの比喩を考えましょう。いま、2人の人間が同じオフィスビル内の、異なる2つの部門にいるとします。オフィスビルがプロセス、部門がAppDomainに相当します。2人は社内便を使って、メッセージや荷物をやり取りします。そこで必要とされる時間や費用は、さほど大きなものではありません。

 次に、同じ2人が国内の別地域に住んでいるとしましょう。この場合は、同じメッセージや荷物を送るにもかなりの郵便代がかかりますし、それを郵便局に持っていくための時間もかかります。時間や費用がかかることが、すなわちオーバーヘッドの増大です。Win32プロセス間の通信でも、同じことが言えます。

 最後に、1人がアメリカ、もう1人がイギリスに住んでいる場合を考えましょう。メッセージや荷物を送るのにかかる時間は大幅に長くなります。また、それほどではないにせよ、費用も増大します。この状況は、2台のマシンをつなぐネットワーク経由の通信に似ています。

 さて、この2人が同じ室内にいたらどうでしょう。通信はずっと速く、ずっと安上がりになります。とにかく、面と向かって話し合い、必要なものを手渡せばいいのですから、封筒も包装もいりません。AppDomain内通信もこれと同じことです。2つのオブジェクト間における最も効率の良い通信方法が、これであることは明らかでしょう。

 ここまでの比喩は、主としてパフォーマンスだけを考えたものでした。今回の記事で実装する.NET Service ManagerではRemotingの別の機能――他のAppDomain内にオブジェクトのインスタンスを作り、それにStart(開始)やStop(停止)などのキーメッセージを送信するという働き――が重要な意味を持ちます。この機能には、MarshalByRefクラスを継承するオブジェクトを使用します。この継承によって、あるAppDomain内にオブジェクトを作成し、それを別のAppDomainから制御することが可能になります。この方法では、参照(アドレス)だけをパッケージ(プロキシ)の形でAppDomain境界の向こうへ送信します。MarshalByRef(参照によるマーシャリング)という名前は、そこから来ています。マーシャリングという言葉でわかりにくければ、「エスコート」と言い替えてもよいでしょう。

 Remoting境界を越えてオブジェクトをマーシャリングする場合、既定では値単位でマーシャリングが実施されます。つまり、データ(値)の完全コピーがシリアル化され、送信されます。ただ、これをRemotingで正しく(つまり、適切に)行おうとすれば、実際にはクラスにSerializableAttributeを追加しなければなりません。上で「既定では」と言いましたが、「リモートデータを扱うには、ほとんどの場合、そうすることが望ましい」と言い替えたほうがいいかもしれません(理由はありますが、ここでは取り上げません)。ただ、今回のケースではMarshalByRefクラスを使用する必要がありますし、扱うのはプロセス内におけるAppDomain間通信ですから、MarshalByRefの使用による負の影響はそれほど大きくありません。

 とにかく、Remotingを使って、ある型のインスタンスを別のAppDomain内に作成します。コードでは、この型をRemoteServiceHandlerと呼んでいます。なぜこの方法を使うかと言うと、型情報がAppDomainにロードされることを避けなければならないからです。これを許すと、アセンブリ全体がメインのAppDomainにロードされ、動的なアンロードができなくなります。今回のアプリケーションにおける我々の目標の1つは、被管理サービス(アセンブリ)の動的なロード/アンロードによって「ホット更新」ができるようにしようということです。そのためには、私の知る限り、個々の一意な被管理サービスアセンブリごとにAppDomainを作成し、その被管理サービスのIService実装をRemotingによって作成・制御する以外に方法がありません(IServiceや被管理サービスの意味がよくわからないという方は、パート1を参照してください)。

 各アセンブリをそれぞれ別個のAppDomainにロードしておくと、それをAppDomain.Unloadメソッドで完全にメモリからアンロードできますし、必要なら更新済みの新しいコピーのロードも、.NET Service Managerのメインプロセスを再始動せずに実行できます。ここでも理屈は同じで、やはり個別のAppDomainを使い、個々の被管理サービスのアセンブリ情報を他から分離することで、これが可能になります。型をメインのAppDomainへ直接ロードしたのでは、そのメインAppDomainを再始動しない限り(したがって、プロセス全体を再始動しない限り)アンロードと更新ができません。既に述べたとおり、アセンブリの型情報をロードしてしまうと、アセンブリ全体がそのAppDomainにロードされます。図1に、この分離化の様子を示します。

図1
図1

それぞれの概念の実装

 関係する概念と技術を高みから概観したところで、今度は具体的な実装を詳しく見ていくことにしましょう。このアプリケーションのエンジンはServiceBrokerアセンブリの中にあります。パート1で述べたとおり、ここには必要なServiceEntryPoint属性とIServiceインタフェースの定義が含まれています。また、被管理サービスのロード、開始、停止、アンロードを行うコードも、子AppDomain中の型とのリモート対話に使われるRemoteServiceHandler型もここに含まれています。

 エンジンの中核はServiceBrokerクラスです。サービス状態そのものや監視対象のディレクトリに変化があったときは、このクラスが「.NET Service Manager」Windowsサービスによって呼び出されます。ServiceBrokerクラスの冒頭部分(リスト1)には、HybridDictionaryの静的/共用インスタンスの宣言がいくつかあります。これは、データと被管理サービス参照のキャッシュに用いられるインスタンスです。HybridDictionary型を選択したのは、一般には小さくて、ときに大きくもなりうるコレクションを扱う際に最もすぐれたパフォーマンスを示す型だからです。

リスト1
private HybridDictionary serviceNames =
    new HybridDictionary(10);
private HybridDictionary serviceAppDomains =
    new HybridDictionary(10);
private HybridDictionary services =
    new HybridDictionary(10);
private HybridDictionary serviceLastModified =
    new HybridDictionary(10);

 serviceNamesディクショナリとserviceLastModifiedディクショナリの役割は、ロードされているサービスについてのデータをキャッシュすることです。serviceNamesは、オリジナルアセンブリの所在へのパスをキーとし、その被管理サービスのServiceEntryPointAttribute.ServiceName値を値として使用します。serviceLastModifiedは、ServiceNameをキーとし、被管理サービスのアセンブリの最終更新日時(DateTime)を値として使用します。他の2つのディクショナリには、重要オブジェクトへの参照が格納されます。まず、serviceAppDomainsには、動的に作成されたAppDomainへの参照がServiceNameをキーとして格納されます。そしてservicesには、被管理サービスのリモート制御(それぞれのAppDomainにある被管理サービスの制御)に使われるRemoteServiceHandlerへの参照が格納されます。

サービスのロードと開始

 次のリスト2に示すのは、アプリケーション機能の中核を成すServiceBroker.StartServiceメソッドの一部です。パラメータはfilePathの1つだけで、これに基づいて目的のDLLを探します。まず、パスからファイル名を取り出して、.NET Service Managerのディレクトリに常に存在する2つのファイルと比較します。これは、これらのファイルを不必要に処理しないための用心です。

リスト2
public void StartService(string filePath)
{
    string serviceName;
    string fileName = filePath.Substring(
        filePath.LastIndexOf("\\") + 1);
    if (fileName.IndexOf("Microsoft.ApplicationBlocks") != -1 ||
        fileName == "ServiceBroker.dll")
        return;
    try
    {
        AssemblyName asmName = null;
        try
        {
            asmName = AssemblyName.GetAssemblyName(filePath);
        }
        catch (Exception ex)
        {
            Logger.WriteToLog(
                String.Format("Could not get assembly name "
                    +"from '{0}'; bypassing that file.",filePath) 
                + " Exception Details: " + ex.ToString()
                ,System.Diagnostics.EventLogEntryType.Warning);
            return;
        }
...

 次にロード手順を開始します。まず、ReflectionのAssemblyName.GetAssemblyNameメソッドによって2つのことを行います。1つは、これが.NETアセンブリなのか、それ以外のDLLなのかを確認することです。このメソッドから例外がスローされたら、おそらく.NETアセンブリではありません。AssemblyName情報の一部は、後でまた必要になります。AssemblyNameをロードしても、AssemblyNameのプロパティに値を埋めるのに必要なメタデータが読み込まれるだけで、アセンブリ自体はメモリにロードされないことに注意してください。

 このメソッドの次のコードブロックをリスト3に示します。この部分の目的は、問題のアセンブリの最新バージョンがロード済みでないかどうかを確かめることです。ロード済みなら、これ以後の実行の一部を省略できます。確かめるには、まず、serviceNamesコレクションにサービス名が含まれていないかどうかを調べます。含まれていれば、現在ディレクトリにあるファイルからサービス名と最終変更時刻を取り出し、それをキャッシュされている最終変更時刻と比較します。これで、新しいバージョンがディレクトリにドロップされたかどうかがわかります。

リスト3
// check if assembly service already loaded
if (this.serviceNames.Contains(filePath))
{
    serviceName = this.serviceNames[filePath].ToString();
    DateTime curTime = File.GetLastWriteTime(filePath);
    // only add the service if it is not already running latest copy
    if (curTime.Ticks <=
      ((DateTime)this.serviceLastModified[serviceName]).Ticks)
    {
      Logger.WriteToLog(
        String.Format("Skipping '{0}' because it is already loaded.",
        serviceName),
        System.Diagnostics.EventLogEntryType.Information);
      return;
    }
    else// new copy, so stop current and clear out cache
    {
      // stop/unload service
      this.UnloadService(serviceName);
      // remove from file path cache
      this.serviceNames.Remove(filePath);
    }
}

 現在ディレクトリ中にあるバージョンがいまロードされているバージョンと同じか、それより古い場合は、再ロードは不要です。ロードを省略したことをログに記入して、関数を抜けます。それ以外の場合は、新しいバージョンがディレクトリにドロップされているので、現在のバージョンをアンロードし、キャッシュから取り除かなければなりません。

 UnloadServiceメソッド(リスト4)は、被管理サービスを停止してアンロードし、それへの参照を静的キャッシュから取り除きます。このメソッドは、どこでも必要な場所で使用できます。

リスト4
private void UnloadService(string serviceName)
{
    RemoteServiceHandler service =
        this.services[serviceName] as RemoteServiceHandler;
    if (service != null)
    {
        try
        {
            service.StopService();
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);
        }
    }
    this.services.Remove(serviceName);
    AppDomain svcDomain =
        this.serviceAppDomains[serviceName] as AppDomain;
    if (svcDomain != null)
    {
        try
        {
            AppDomain.Unload(svcDomain);
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);
        }
    }
    this.serviceAppDomains.Remove(serviceName);
    this.serviceLastModified.Remove(serviceName);
}

 UnloadServiceメソッドでは、まずサービス名に基づいて被管理サービスのRemoteServiceHandlerへの参照を取得し、その参照からIService.StopService実装を呼んでシャットダウンを試みます(RemoteServiceHandler.StopServiceは、現在処理中のどのサービスに対してもIService.StopService実装を呼び出します)。RemoteServiceHandlerについては後で詳しく説明します。このメソッドは、次に被管理サービスのAppDomainへの参照を取得し、AppDomain.Unloadでのアンロードを試みたのち、最後に各キャッシュからその他の参照を取り除きます。ただし、serviceNamesキャッシュだけは、リスト3の最終行で見たとおり、呼び出しを行ったコードの中でクリアしなければなりません。

 被管理サービスの新バージョンをロードする必要があるかどうか(既にロードされているときは、アンロードが必要かどうか)を調べた後で、AppDomainの作成と被管理サービスのリモートロードに進みます。

リスト5
AppDomain svcDomain = null;
try
{
    AppDomainSetup setup = new AppDomainSetup();
    setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
    setup.PrivateBinPath = setup.ApplicationBase;
    setup.ApplicationName = asmName.FullName;
    setup.ShadowCopyDirectories = setup.ApplicationBase;
    setup.ShadowCopyFiles = "true";
    svcDomain = 
        AppDomain.CreateDomain(asmName.FullName, null, setup);
}
catch (Exception ex)
{
    Logger.LogException(
        new ApplicationException(
            String.Format("Could not create an AppDomain for '{0}'; "
            + "bypassing that assembly.", asmName.FullName), ex));
    return;
}

 これは、被管理サービスの実行に使う新しいAppDomainをセットアップするコードです。AppDomainSetupオブジェクトを使用しているのは、AppDomain.CreateDomainのオーバーロードにはないオプションがあるためです。ここでは、ディレクトリプロパティを、現アプリケーションディレクトリを指し示すように設定することと、ファイルのシャドウコピーを作成するよう指示することが重要です。シャドウコピーを作成することによって、アセンブリがアプリケーションディレクトリでロックされることを防止できます。ここで例外がスローされることはまずないと思われますが、それでも、何が起こったかをログに記録するためと、スレッドを例外で終わらせないために、例外を正確にキャッチするようにしました。アプリケーションにユーザインタフェースがないときは、こうやって例外をキャッチし、ログに記録することをお勧めします。

 さてこれで、被管理サービスを実行するための専用のAppDomainが作成できました。では、新しいAppDomainに被管理サービスをロードしましょう。型データをメインのAppDomainにロードせずにこれを行うには、上で触れたRemoteServiceHandlerを使用します。この型が何をするかと言えば、既知の型(被管理サービスアセンブリでなく、ServiceBrokerアセンブリで宣言されたもの)を返すことです。その型を使って子AppDomain内にインスタンスを作成できます。これはMarshalByRefを継承しているので、インスタンスは実際に子ドメインの中に置かれており、我々はそれに向けてメッセージを送信することになります。

リスト6
RemoteServiceHandler svc = null;
try
{
    svc = (RemoteServiceHandler)
        svcDomain.CreateInstanceFromAndUnwrap(
        svcDomain.BaseDirectory + "\\ServiceBroker.dll",
        "ServiceBroker.RemoteServiceHandler");
}
catch (Exception ex)
{
    AppDomain.Unload(svcDomain);
    Logger.LogException(
        new AssemblyLoadException(
            "Could not load ServiceBroker remote service handler,"
            + " bypassing that file.",
        asmName.FullName, ex));
    return;
}

 リスト6のコードでは、被管理サービスのドメインに置かれるRemoteServiceHandlerインスタンスを作成しています。具体的には、そのAppDomainインスタンスに対してCreateInstanceFromAndUnwrapメソッドを呼び出します。このメソッドは、いわば便利なヘルパであり、実質的には、AppDomain.CreateInstanceFromObjectHandle.UnWrapという2つのメソッドを併せた働きをします。CreateInstanceFromメソッドからObjectHandleが返されるので、そのObjectHandleに対してUnWrapメソッドを呼び出し、被管理サービスドメイン内にあるRemoteServiceHandlerへの透過プロキシを作るという手順です。

 前出の比喩を使って表現すれば、この時点で、相手が近くのオフィスにいることを知っていて、その人宛てのオフィス間郵便のアドレスも知っている状態になります。これまでは、情報のやり取りに関わる人全員が同じ部屋の中にいました。ここで初めて、離れた場所にいる人を相手にできるようになります。

 ここの例外ハンドラでは、まずAppDomain.Unloadを呼び出していることに注意してください。既にロード済みのAppDomainがある場合、何か不測の事態が起こって、そのAppDomainがもはや不要になったとき、それをいつまでもメモリにとどめたくはないからです。

 RemoteServiceHandlerについて、Remoting機能との関連でいくつか考えておかなければならないことがあります。まず、MarshalByRefからの継承を指定したことです。これについては既に説明したので、ここでは繰り返しません。次に、InitializeLifetimeServiceをオーバーライドして、nullを返させることです。なぜそうするかと言えば、プロキシが期限切れになって、リモートオブジェクトが回収されてしまうことを防ぐためです。このメソッドからnullを返すと、インスタンスの寿命管理が実質的に禁止されます。今回の例では、作成する被管理サービスとの間に有効なリンクを維持しておく必要があります。そうしないと、将来(数週間後、あるいは数か月後)、その被管理サービスに対してStopServiceメソッドを呼び出すことができないからです。

 被管理サービスのAppDomain内にあるRemoteServiceHandlerインスタンスへの透過的なプロキシが得られたら、さらにもう1歩進んで、そのドメイン中でサービスを開始します。そのためには、リスト7に示すとおり、RemoteServiceHandlerLoadServiceメソッドを呼び出します。これにより、他のAppDomain内のインスタンスにメッセージが送られ、これこれという名前のサービスをロードするように伝達されます(ちょうど、別室にいる同僚にメモを送り、あることで手助けをしてくれるよう頼む感じでしょうか)。プロキシで何かをするということは、相手インスタンスに対して直接何かをするのではなく、メッセージのやり取りで済ませることだと覚えておいてください。

リスト7
try
{
    if (!svc.LoadService(asmName.FullName))
    {
        AppDomain.Unload(svcDomain);
        Logger.WriteToLog(
            String.Format("No ServiceEntryPointAttribute was "
                + "found for assembly '{0}'.",
            asmName.FullName),
            System.Diagnostics.EventLogEntryType.Warning);
        return;
    }
}
catch (Exception ex)
{
    AppDomain.Unload(svcDomain);
    Logger.LogException(ex);
    return;
}

 LoadServiceには3つの重要な働きがあります。まず、目的のアセンブリをロードすることです。今回のロードには、先に.NETアセンブリかどうかを確かめるときに取り出しておいたAssemblyName.FullNameプロパティを使用します。被管理サービスアセンブリのロードが済むと、今度はReflectionのAssembly.GetCustomAttributesメソッドを用いて、そのアセンブリのServiceEntryPointAttributeを見つけようとします。ロードされたアセンブリにServiceEntryPoint属性が適用されていれば、LoadServiceはサービスエントリポイントの型名とフレンドリ名を取り出します。最後に、その属性から得られた情報に基づいて、IServiceを実装する型インスタンスを作成します。具体的には、そのアセンブリに対してCreateInstanceを呼び出し、サービスエントリポイントの型名を引き渡します。次のリスト8を見てください。

リスト8
try
{
    this.service = (IService)assembly.CreateInstance(
        this.serviceEntryPointType, true);
}
catch (Exception ex)
{
    throw new TypeInitializationException(
        this.serviceEntryPointType, ex);
}

 例外の発生もなくすべてが進めば、このメソッドからはtrueが返され(メッセージがServiceBrokerに送り返され)、指定されたアセンブリの被管理サービスが正しくロードされたことが伝達されます。この時点で、ServiceBrokerの魔法はほぼ完成です。つまり、新しいAppDomainが作成され、そこへ目的の被管理サービスがリモートロードされています。あと残るのは、その被管理サービスを開始すること、そしてのちにサービスの停止とアンロードが必要になったとき使用できるよう、それへの参照をキャッシュに記憶しておくことです。リスト9を参照してください。

リスト9
svc.StartService();
serviceName = svc.ServiceName;
this.serviceNames.Add(filePath, serviceName);
this.services.Add(serviceName, svc);
this.serviceLastModified.Add(
    serviceName, File.GetLastWriteTime(filePath));
this.serviceAppDomains.Add(serviceName, svcDomain);

 リスト9に示すコードの実行が終わった時点で、サービスは既に開始されています。つまり、当該被管理サービスの作者がIService.StartService実装に含めておいたコードが、既に実行されています。パート1で被管理サービスのセットアップ方法を示した際、私がSampleServiceでやったことは、イベントログにメッセージを書き込むことだけでした。しかし、一般の被管理サービスでは、タイマを始動させるなどして、定期的に何らかのタスクを実行させるのが普通でしょう。

サービスの停止

 もちろん、これまでやってきたことの裏返しとして、被管理サービスを停止させるための作業も必要です。しかし、困難な仕事はすべて終わっていて、停止にともなう作業はずっと簡単ですから安心してください。被管理サービスアセンブリが削除され、あるいはServicesアプレット中で.NET Service Managerが停止されると、ServiceBrokerStopServiceメソッド(リスト10)が呼び出されます。見てわかるとおり、このメソッドは停止すべき被管理サービスのファイルパスを取り出し、それを使ってサービス名キャッシュ内で当該サービスを探します。そして見つかれば、UnloadServiceメソッド(前出のリスト4)を呼び出し、それをサービス名キャッシュから取り除きます。万事これほど簡単に運ぶといいのですが……。

リスト10
public void StopService(string filePath)
{
    if (this.serviceNames.Contains(filePath))
    {
        this.UnloadService(Convert.ToString(
            this.serviceNames[filePath]));
        this.serviceNames.Remove(filePath);
    }
}

 以上で、.NET Service Managerの魔法の源泉をあらかた見終わりました。もちろん、そのほとんど(特に、AppDomain、Remoting、Reflectionを取り巻く諸概念)は、どのようなアプリケーションでも便利に使用でき、アセンブリの動的なロード、更新、アンロードといった便利な機能を実現できます。

残務整理

 .NET Service Managerとは切っても切り離せないのに、説明が長くなるために本稿では取り上げなかった事柄がいくつかあります。その第一は、Windowsサービスコードそのものです。これについては既に多くの資料(.NETでWindowsサービスを構築する方法などを扱った記事の類)が出回っていて、私から価値ある何かを付け加えられるとは思えないという事情もありました。あえて言うならば、実際のWindowsサービスでは、ServiceBrokerインスタンスを作成しなければならず、FileSystemWatcherを使ってアプリケーションディレクトリ内で新しいDLLを探すことも必要である、ということだけ指摘しておきたいと思います。ディレクトリに新しいDLLが追加されたり、既存のDLLが変更されたりすると、そのDLLのServiceBroker.StartServiceが呼び出されます。また、あるDLLが削除されると、そのServiceBroker.StopServiceが呼び出されます。Windowsの[サービス]アプレットを使って開始または停止した場合や、コンソールからコマンドを発行した場合は、ディレクトリ中のすべてのDLLが順繰りに調べられ、目的のDLLのServiceBrokerインスタンスに対してStartServiceメソッドもしくはStopServiceメソッドが呼び出されます。

 上記のとおりディレクトリを監視していて、1つまずいことに気がつきました。それは、ファイルをディレクトリにドロップしたとき、同じファイルに対してFileSystemWatcherが複数のイベントを発生させることがあるという点です。そこで、一度のファイル変更が複数回処理されるのを防ぐため、変更から変更まで2秒間待つというチェックを入れてみました。残念ながら、この方法では複数のDLLを同時にドロップしたときにエラーが起こるという別のバグが発生したため、チェックを手直しして、変更されたファイルが前回変更されたファイルと別物かどうかを調べるようにしました。つまり、適用後2秒以上経過したファイル変更か、別ファイルへの変更だけを処理するという規則です。

 もう1つ簡単に触れておきたい事柄に、Configクラスがあります。パート1を読んだ方はご記憶でしょうが、これは被管理サービスごとに別個の設定ファイルを持てるようにするためのクラスです。つまり、このクラスのインスタンスは、被管理サービスアセンブリ名に「.config」を付加したファイルを探すようにFileSystemWatcherをセットアップします(.NETが.EXEアプリケーションを探すのと同様です)。具体的には、Assembly.GetCallingAssembly().GetName(false).CodeBaseを呼び出して、URLに似た構文を標準のWindows構文に置き換えて末尾に「.config」を付加し、用意された文字列インデクサによる設定要求があるまでロードを遅らせます。

 .NET Service Managerは、既に私と仲間たちにとっては開発に役立つ有用なサービスとなっています。読者にとっても同様であることを願っています。このアプリケーション全体の課題として、なかなか解決が難しいだろうと思われる問題が1つあることを申し上げておきます。それは、従属DLLを持つ被管理サービスを配備するときには、参照されるDLLを先に配備しておかなければならないということです。そうしないと、被管理サービスに対してStartServiceを呼び出したとき、参照すべきアセンブリが見つからないというエラーになります。一定時間待ってからアセンブリの処理を開始するよう、何らかの時限式キューを作ることも考えましたが、完全には信頼できるものになりません。おそらく、配備手順の中で記録しておいて、忘れないようにするのが最良の解決策だと思います。

参考資料

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

J. Ambrose Little(J. Ambrose Little)

ASPAlliance編集長。ASPInsiderであり、Microsoft ASP.NET MVPでもある。現在はWebアーキテクトとして、フロリダ州タンパにある大手信用組合に勤務。これまでに、Verizon社のコンサルタントとしてXML Webサービスと中間層コンポーネントを開発し、BOk Financial社のWebサービス部門で同社イントラネット用にASP.NETアプリケーションを開発。...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/187 2005/12/06 18:04

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング