.NET Frameworkを使用した DLL Hell のデプロイと解決の簡素化
スティーブン・プラットシュナー
Microsoft Corporation
更新日: 2001 年 11 月
概要:この記事では、アセンブリの概念について説明し、.NET Frameworkがアセンブリを使用してバージョン管理と配置の問題を解決する方法について説明します。 (16ページ印刷)
内容
はじめに
問題の説明
ソリューションの特性
アセンブリ: 構成要素
バージョン管理と共有
バージョン ポリシー
デプロイ
まとめ
はじめに
Microsoft® .NET Frameworkでは、アプリケーションのデプロイを簡略化し、DLL Hell を解決することを目的としたいくつかの新機能が導入されています。 エンド ユーザーと開発者はどちらも、現在のコンポーネント ベースのシステムで発生する可能性があるバージョン管理とデプロイの問題に精通しています。 たとえば、事実上すべてのエンド ユーザーがマシンに新しいアプリケーションをインストールしており、既存のアプリケーションが不思議なことに動作を停止していることがわかります。 また、ほとんどの開発者は Regedit で時間を費やし、COM クラスをアクティブにするために必要なすべてのレジストリ エントリの一貫性を維持しようとしています。
DLL Hell を解決するために.NET Frameworkで使用される設計ガイドラインと実装手法は、MICROSOFT Windows® 2000 で行われた作業に基づいて構築されています。これは、DLL 地獄の終わりにある Rick Anderson、および David D'Souza、BJ Whalen、Peter Wilson による「アプリケーションでのサイド バイ サイド コンポーネント共有の実装 (展開)」で説明されています。 .NET Frameworkでは、.NET プラットフォーム上のマネージド コードを使用して構築されたアプリケーションのアプリケーション分離やサイド バイ サイド コンポーネントなどの機能を提供することで、この前の作業を拡張します。 また、Windows XP には、COM クラスや Win32 DLL など、アンマネージド コードに対して同じ分離機能とバージョン管理機能が用意されています (詳細については、「How To Build and Service Isolated Applications and Side-by-Side Assemblies for Windows XP 」を参照してください)。
この記事では、 アセンブリ の概念について説明し、.NET がアセンブリを使用してバージョン管理と配置の問題を解決する方法について説明します。 特に、アセンブリの構造、アセンブリの名前付け方法、およびコンパイラと共通言語ランタイム (CLR) がアセンブリを使用してアプリケーションの一部間のバージョン依存関係を記録および適用する方法について説明します。 また、アプリケーションと管理者がバージョンポリシーと呼ばれるものを通じてバージョン管理の動作をカスタマイズする方法についても説明 します。
アセンブリを導入して説明した後、いくつかの展開シナリオが表示され、.NET Frameworkで使用できるさまざまなパッケージ化と配布オプションのサンプリングが提供されます。
問題の説明
バージョン管理
お客様の観点から見ると、最も一般的なバージョン管理の問題は、DLL Hell と呼ばれるものです。 DLL Hell とは、複数のアプリケーションがダイナミック リンク ライブラリ (DLL) やコンポーネント オブジェクト モデル (COM) クラスなどの共通コンポーネントを共有しようとしたときに発生する一連の問題を指します。 最も一般的なケースでは、1 つのアプリケーションで、コンピューター上のバージョンと下位互換性のない新しいバージョンの共有コンポーネントがインストールされます。 インストールしたばかりのアプリケーションは正常に動作しますが、以前のバージョンの共有コンポーネントに依存していた既存のアプリケーションは機能しなくなる可能性があります。 場合によっては、問題の原因がさらに微妙になります。 たとえば、ユーザーが一部の Web サイトにアクセスする副作用として Microsoft ActiveX® コントロールをダウンロードするシナリオを考えてみましょう。 コントロールがダウンロードされると、コンピューター上に存在していたコントロールの既存のバージョンが置き換えられます。 コンピューターにインストールされているアプリケーションがこのコントロールを使用すると、動作が停止する可能性もあります。
多くの場合、アプリケーションが動作を停止したことをユーザーが検出するまでに大幅な遅延が発生します。 その結果、多くの場合、アプリに影響を与えた可能性のあるマシンに変更が加えられたことを思い出すことが困難になります。 ユーザーは 1 週間前に何かをインストールしたことを覚えているかもしれませんが、そのインストールと現在表示されている動作の間に明確な相関関係はありません。 さらに悪いことに、ユーザー (またはサポート担当者) が何が間違っているかを判断するのに役立つ診断ツールは現在ほとんどありません。
これらの問題の理由は、アプリケーションのさまざまなコンポーネントに関するバージョン情報がシステムによって記録または適用されないことです。 また、1 つのアプリケーションに代わってシステムに加えられた変更は、通常、コンピューター上のすべてのアプリケーションに影響します。変更から完全に分離されたアプリケーションを今日構築するのは簡単ではありません。
分離されたアプリケーションをビルドするのが難しい理由の 1 つは、現在のランタイム環境では通常、コンポーネントまたはアプリケーションの 1 つのバージョンのみをインストールできるということです。 この制限は、コンポーネント作成者が下位互換性を維持する方法でコードを記述する必要があることを意味します。そうしないと、新しいコンポーネントをインストールするときに既存のアプリケーションが壊れる危険性があります。 実際には、永遠に下位互換性のあるコードを記述することは、不可能ではないにしても非常に困難です。 .NET では、 サイド バイ サイド の概念は、バージョン管理のストーリーの中核です。 サイド バイ サイドは、同じコンポーネントの複数のバージョンを同時にマシンにインストールして実行する機能です。 サイド バイ サイドをサポートするコンポーネントでは、作成者は、異なるアプリケーションで共有コンポーネントの異なるバージョンを自由に使用できるため、厳密な下位互換性を維持することに必ずしも結び付けられているわけではありません。
展開とインストール
現在のアプリケーションのインストールは、複数ステップのプロセスです。 通常、アプリケーションをインストールするには、多数のソフトウェア コンポーネントをディスクにコピーし、それらのコンポーネントをシステムに記述する一連のレジストリ エントリを作成する必要があります。
レジストリ内のエントリとディスク上のファイルを分離すると、アプリケーションをレプリケートしてアンインストールすることが非常に困難になります。 また、レジストリ内の COM クラスを完全に記述するために必要なさまざまなエントリ間の関係は非常に緩いです。 多くの場合、これらのエントリには、ドキュメント拡張機能やコンポーネント カテゴリを登録するために作成されたエントリをメンションするためではなく、コクラス、インターフェイス、typelibs、DCOM アプリ ID のエントリが含まれます。 多くの場合、これらを手動で同期したままにしておく必要があります。
最後に、COM クラスをアクティブ化するには、このレジストリフットプリントが必要です。 これにより、適切なレジストリ エントリを作成するために各クライアント コンピューターにタッチする必要があるため、分散アプリケーションを展開するプロセスが大幅に複雑になります。
これらの問題は、主に、コンポーネント自体とは別にコンポーネントの説明が保持されることによって引き起こされます。 言い換えると、アプリケーションは自己記述型でも自己完結型でもありません。
ソリューションの特性
.NET Frameworkは、先ほど説明した問題を解決するための次の基本的な機能を提供する必要があります。
- アプリケーションは自己記述である必要があります。 自己記述型のアプリケーションでは、レジストリへの依存関係が削除され、影響がゼロのインストールが可能になり、アンインストールとレプリケーションが簡略化されます。
- バージョン情報を記録して適用する必要があります。 実行時に適切なバージョンの依存関係が読み込まれるように、バージョン管理のサポートをプラットフォームに組み込む必要があります。
- 「最後の既知の良い」を覚えておく必要があります。 アプリケーションが正常に実行されると、プラットフォームは連携した一連のコンポーネント (バージョンを含む) を記憶する必要があります。 さらに、管理者がアプリケーションをこの "最後の既知の正常" 状態に簡単に戻せるようにするツールを提供する必要があります。
- サイド バイ サイド コンポーネントのサポート。 複数のバージョンのコンポーネントをマシンに同時にインストールして実行できるようにすると、呼び出し元は、知らないうちにバージョン "強制" ではなく、読み込むバージョンを指定できます。 .NET Frameworkでは、フレームワーク自体の複数のバージョンを 1 台のコンピューター上に共存させることで、一歩ずつ進みます。 管理者は必要に応じて、異なるバージョンの.NET Frameworkで異なるアプリケーションを実行することを選択できるため、アップグレードのストーリーが大幅に簡略化されます。
- アプリケーションの分離。 .NET Frameworkでは、他のアプリケーションに代わってマシンに加えられた変更の影響を受けることができないアプリケーションを簡単に作成でき、実際には既定のアプリケーションにする必要があります。
アセンブリ: 構成要素
アセンブリは、先ほど説明したバージョン管理とデプロイの問題を解決するために.NET Frameworkによって使用される構成要素です。 アセンブリは、型とリソースの配置単位です。 アセンブリは多くの点で、今日の世界の DLL に相当します。本質的に、アセンブリは "論理 DLL" です。
アセンブリは、マニフェストと呼ばれるメタデータを使用して自己記述します。 .NET ではメタデータを使用して型を記述するのと同様に、メタデータを使用して型を含むアセンブリも記述します。
アセンブリは、配置よりもはるかに多くの機能を持ちます。 たとえば、.NET のバージョン管理はアセンブリ レベルで行われます。モジュールや型などの小さいものはバージョン管理されません。 また、アセンブリはアプリケーション間でコードを共有するために使用されます。 型が含まれるアセンブリは、型の ID の一部です。
コード アクセス セキュリティ システムは、そのアクセス許可モデルの中核にあるアセンブリを使用します。 アセンブリの作成者は、コードを実行するために必要な一連のアクセス許可をマニフェストに記録し、管理者はコードが含まれているアセンブリに基づいてコードにアクセス許可を付与します。
最後に、アセンブリは型の可視性境界を確立し、型への参照を解決するための実行時スコープとして機能するという点で、型システムとランタイム システムの中核でもあります。
アセンブリ マニフェスト
具体的には、マニフェストにはアセンブリに関する次のデータが含まれています。
- ID。 アセンブリの ID は、単純なテキスト名、バージョン番号、省略可能なカルチャ、およびアセンブリが共有用に構築された場合はオプションの公開キーの 4 つの部分で構成されます (以下の「共有アセンブリ」のセクションを参照)。
- ファイルの一覧。 マニフェストには、アセンブリを構成するすべてのファイルの一覧が含まれます。 マニフェストは、ファイルごとに、その名前と、マニフェストの作成時にその内容の暗号化ハッシュを記録します。 このハッシュは実行時に検証され、デプロイ ユニットに一貫性があることを確認します。
- 参照されたアセンブリ。 アセンブリ間の依存関係は、呼び出し元のアセンブリのマニフェストに格納されます。 依存関係情報にはバージョン番号が含まれています。これは、依存関係の正しいバージョンが読み込まれるように実行時に使用されます。
- エクスポートされた型とリソース。 型とリソースで使用できる可視性オプションには、"自分のアセンブリ内でのみ表示される" と "アセンブリ外の呼び出し元に表示される" などがあります。
- アクセス許可要求。 アセンブリに対するアクセス許可要求は、3 つのセットにグループ化されます。1) アセンブリの実行に必要なもの、2) 必要なアセンブリが付与されていない場合でもアセンブリには何らかの機能が残り、3) 作成者がアセンブリを許可したくないものがあります。
IL 逆アセンブラー (Ildasm) SDK ツールは、アセンブリ内のコードとメタデータを調べることに役立ちます。 図 1 は、Ildasm によって表示されるマニフェストの例です。 .assembly ディレクティブはアセンブリを識別し、.assembly extern ディレクティブには、このアセンブリが依存する他のアセンブリに関する情報が含まれています。
図 1. IL 逆アセンブラーによって表示されるマニフェストの例
アセンブリ構造
これまで、アセンブリは主に論理概念として説明されてきました。 このセクションでは、アセンブリが物理的にどのように表現されるかを説明することで、アセンブリをより具体的なものにするのに役立ちます。
一般に、アセンブリは、アセンブリ メタデータ (マニフェスト)、型を記述するメタデータ、型を実装する中間言語 (IL) コード、リソースのセットの 4 つの要素で構成されます。 これらのすべてが各アセンブリに存在するわけではありません。 マニフェストのみが厳密に必要ですが、アセンブリに意味のある機能を提供するには、型またはリソースが必要です。
これら 4 つの要素を "パッケージ化" する方法には、いくつかのオプションがあります。たとえば、図 2 は、アセンブリ全体 (マニフェスト、型メタデータ、IL コード、リソース) を含む 1 つの DLL を示しています。
図 2. すべてのアセンブリ要素を含む DLL
または、アセンブリの内容を複数のファイルに分散することもできます。 図 3 では、作成者は、ユーティリティ コードを別の DLL に分割し、大きなリソース ファイル (この場合は JPEG) を元のファイルに保持することを選択しました。 これが行われる理由の 1 つは、コードのダウンロードを最適化することです。 .NET Frameworkは、ファイルが参照されている場合にのみダウンロードされるため、アクセス頻度の低いコードまたはリソースがアセンブリに含まれている場合、それらを個々のファイルに分割すると、ダウンロード効率が向上します。 複数のファイルを使用するもう 1 つの一般的なシナリオは、複数の言語のコードで構成されるアセンブリをビルドすることです。 この場合は、各ファイル (モジュール) を個別にビルドし、.NET Framework SDK (al.exe) で提供されているアセンブリ リンカー ツールを使用してアセンブリにグループ化します。
図 3: 複数のファイルに分散されたアセンブリ要素
バージョン管理と共有
DLL Hell の主な原因の 1 つは、コンポーネント ベースのシステムで現在使用されている共有モデルです。 既定では、個々のソフトウェア コンポーネントはマシン上の複数のアプリケーションで共有されます。 たとえば、インストール プログラムが DLL をシステム ディレクトリにコピーしたり、COM レジストリにクラスを登録したりするたびに、そのコードがコンピューター上で実行されている他のアプリケーションに影響を及ぼす可能性があります。 特に、既存のアプリケーションがその共有コンポーネントの以前のバージョンを使用していた場合、そのアプリケーションは新しいバージョンの使用を自動的に開始します。 共有コンポーネントが厳密に下位互換性がある場合、これは問題ありませんが、多くの場合、下位互換性を維持することは困難です(不可能ではないにしても)。 下位互換性が維持されていない場合、または維持できない場合、多くの場合、他のアプリケーションがインストールされている場合の副作用としてアプリケーションが壊れます。
.NET の原則設計ガイドラインは、分離されたコンポーネント (またはアセンブリ) のガイドラインです。 アセンブリを分離することは、アセンブリにアクセスできるのは 1 つのアプリケーションだけであり、マシン上の複数のアプリケーションによって共有されるのではなく、他のアプリケーションによってシステムに加えられた変更の影響を受けることができません。 分離により、開発者はアプリケーションで使用されるコードを完全に制御できます。 分離されたアセンブリ (アプリケーション プライベート) は、.NET アプリケーションの既定値です。 分離されたコンポーネントに対する傾向は、.local ファイルの導入により Microsoft Windows 2000 で開始されました。 このファイルは、要求されたコンポーネントを検索しようとしたときに、OS ローダーと COM の両方が最初にアプリケーション ディレクトリを検索するために使用されました。 (MSDN ライブラリの「 アプリケーションでのサイド バイ サイド コンポーネント共有の実装」の関連記事を参照してください)。
ただし、アプリケーション間でアセンブリを共有する必要がある場合があります。 すべてのアプリケーションが System.Windowns.Forms、System.Web、または共通のWeb Forms コントロールの独自のコピーを保持することは明らかに意味がありません。
.NET では、アプリケーション間でコードを共有することは明示的な決定です。 共有されるアセンブリには、いくつかの追加要件があります。 具体的には、共有アセンブリは、同じアセンブリの複数のバージョンを同じマシンにインストールして実行できるように、または同じプロセス内でも同時に実行できるように、サイド バイ サイドでサポートする必要があります。 さらに、共有アセンブリの名前付け要件が厳しくなります。 たとえば、共有されるアセンブリには、グローバルに一意の名前が必要です。
分離と共有の両方が必要な場合、2 種類のアセンブリについて考える必要があります。 これは、2 つの間に実際の構造上の違いは存在しないという点で、むしろ、1 つのアプリケーションにプライベートにするか、多くのアプリケーション間で共有するかという、それらがどのように使用されるかという点で、かなり緩やかな分類です。
Application-Private アセンブリ
アプリケーション プライベート アセンブリは、1 つのアプリケーションにのみ表示されるアセンブリです。 これは .NET で最も一般的なケースであると想定しています。 プライベート アセンブリの名前付け要件は単純です。アセンブリ名は、アプリケーション内でのみ一意である必要があります。 グローバルに一意な名前は必要ありません。 アプリケーション開発者がアプリケーションに分離するアセンブリを完全に制御できるため、名前を一意に保つことは問題ではありません。
アプリケーション プライベート アセンブリは、使用されるアプリケーションのディレクトリ構造内に配置されます。 プライベート アセンブリは、アプリケーション ディレクトリまたはそのサブディレクトリに直接配置できます。 CLR は、プローブと呼ばれるプロセスを通じてこれらのアセンブリ を検索します。 プローブは、アセンブリ名とマニフェストを含むファイルの名前のマッピングです。
具体的には、CLR はアセンブリ参照に記録されたアセンブリの名前を受け取り、".dll" を追加して、アプリケーション ディレクトリでそのファイルを検索します。 このスキームには、ランタイムがアセンブリによって名前付けされたサブディレクトリ、またはアセンブリのカルチャによって名前付けされたサブディレクトリ内で検索されるバリアントがいくつかあります。 たとえば、開発者は、"de" というサブディレクトリ内のドイツ語にローカライズされたリソースを含むアセンブリを、"es" というディレクトリ内のスペイン語に展開することを選択できます。(詳細については、.NET Framework SDK ガイドを参照してください)。
前述のように、各アセンブリ マニフェストには、その依存関係に関するバージョン情報が含まれています。 開発者はアプリケーション ディレクトリに展開されるアセンブリを完全に制御できるため、このバージョン情報はプライベート アセンブリには適用されません。
共有アセンブリ
.NET Frameworkでは、共有アセンブリの概念もサポートされています。 共有アセンブリは、マシン上の複数のアプリケーションで使用されるアセンブリです。 .NET では、アプリケーション間でコードを共有することは明示的な決定です。 共有アセンブリには、現在発生している共有の問題を回避するための追加要件がいくつかあります。 前述のサイド バイ サイドのサポートに加えて、共有アセンブリの名前付け要件ははるかに厳しくなります。 たとえば、共有アセンブリにはグローバルに一意の名前が必要です。 また、システムは "名前の保護" を提供する必要があります。つまり、他のユーザーが別のアセンブリ名を再利用できないようにする必要があります。 たとえば、グリッド コントロールのベンダーで、アセンブリのバージョン 1 をリリースしたとします。 作成者は、バージョン 2 またはグリッド コントロールであると主張するアセンブリを他のユーザーが解放できないことを保証する必要があります。 .NET Frameworkでは、厳密な名前と呼ばれる手法 (次のセクションで詳しく説明) を使用して、これらの名前付け要件をサポートしています。
通常、アプリケーション作成者は、アプリケーションで使用される共有アセンブリを同じレベルで制御できません。 その結果、共有アセンブリへのすべての参照でバージョン情報がチェックされます。 さらに、.NET Frameworkを使用すると、アプリケーションと管理者は、バージョン ポリシーを指定して、アプリケーションによって使用されるアセンブリのバージョンをオーバーライドできます。
共有アセンブリは必ずしも 1 つのアプリケーションにプライベートに展開されるとは限りませんが、特に xcopy デプロイが要件である場合は、そのアプローチは引き続き有効です。 プライベート アプリケーション ディレクトリに加えて、アセンブリの場所を記述するコードベースがアプリケーションの構成ファイルに指定されている限り、共有アセンブリをグローバル アセンブリ キャッシュまたは任意の URL に展開することもできます。 グローバル アセンブリ キャッシュは、複数のアプリケーションで使用されるアセンブリのマシン全体のストアです。 前述のように、キャッシュへのデプロイは要件ではありませんが、これを行うことにはいくつかの利点があります。 たとえば、アセンブリの複数のバージョンのサイド バイ サイド ストレージは自動的に提供されます。 また、管理者はストアを使用して、マシン上のすべてのアプリケーションで使用するバグ修正プログラムまたはセキュリティパッチをデプロイできます。 最後に、グローバル アセンブリ キャッシュへのデプロイに関連するいくつかのパフォーマンスの向上があります。 1 つ目は、下の「厳密な名前」セクションで説明されているように、厳密な名前署名の検証です。 2 つ目のパフォーマンスの向上には、ワーキング セットが含まれます。 複数のアプリケーションが同じアセンブリを同時に使用している場合、そのアセンブリをディスク上の同じ場所から読み込むと、OS によって提供されるコード共有動作が利用されます。 これに対し、複数の異なる場所 (アプリケーション ディレクトリ) から同じアセンブリを読み込むと、同じコードの多数のコピーが読み込まれます。 エンド ユーザーのコンピューター上のキャッシュへのアセンブリの追加は、通常、Windows インストーラーまたはその他のインストール テクノロジに基づくセットアップ プログラムを使用して実行されます。 アセンブリは、一部のアプリケーションを実行したり、Web ページを参照したりした場合の副作用としてキャッシュに入ることはありません。 代わりに、アセンブリをキャッシュにインストールするには、ユーザー側で明示的なアクションが必要です。 Windows XP および Visual Studio .NET に付属する Windows インストーラー 2.0 は、アセンブリ、アセンブリ キャッシュ、および分離されたアプリケーションの概念を完全に理解するように強化されました。 つまり、オンデマンド インストールやアプリケーションの修復など、すべての Windows インストーラー機能を .NET アプリケーションで使用できるようになります。
多くの場合、開発およびテスト マシン上のキャッシュにアセンブリを追加するたびにインストール パッケージをビルドすることは実用的ではありません。 その結果、.NET SDK には、アセンブリ キャッシュを操作するためのツールがいくつか含まれています。 1 つ目は gacutil というツールで、アセンブリをキャッシュに追加して後で削除できます。 /i スイッチを使用して、アセンブリをキャッシュに追加します。
gacutil /i:myassembly.dll
See the .NET Framework SDK documentation for a full description of the
options supported by gacutil.
その他のツールは、Windows エクスプローラーと.NET Framework構成ツールを使用してキャッシュを操作できる Windows シェル拡張機能です。 シェル拡張機能にアクセスするには、Windows ディレクトリの下にある "assembly" サブディレクトリに移動します。 .NET Framework構成ツールは、コントロール パネルの [管理ツール] セクションにあります。
図 4 は、シェル拡張機能を使用したグローバル アセンブリ キャッシュのビューを示しています。
図 4: グローバル アセンブリ キャッシュ
厳密な名前
厳密な名前は、共有アセンブリに関連付けられているより厳密な名前付け要件を有効にするために使用されます。 厳密な名前には、次の 3 つの目標があります。
- 名前の一意性。 共有アセンブリには、グローバルに一意の名前が必要です。
- 名前のスプーフィングを防止します。 開発者は、他の誰かがあなたのアセンブリの後続のバージョンをリリースすることを望んでいません。誤って、または意図的に、自分から来たと誤って主張します。
- 参照時に ID を指定します。 アセンブリへの参照を解決する場合、読み込まれるアセンブリが予想される発行元から取得されたことを保証するために、厳密な名前が使用されます。
厳密な名前は、標準の公開キー暗号化を使用して実装されます。 一般に、このプロセスは次のように機能します。アセンブリの作成者はキー ペアを生成し (または既存のものを使用して)、マニフェストを含むファイルに秘密キーで署名し、公開キーを呼び出し元が使用できるようにします。 アセンブリへの参照が行われると、呼び出し元は、厳密な名前の生成に使用される秘密キーに対応する公開キーを記録します。 図 5 は、メタデータへのキーの格納方法や署名の生成方法など、開発時のこのプロセスのしくみを示しています。
このシナリオは、"Main" という名前のアセンブリであり、"MyLib" という名前のアセンブリを参照します。MyLib には共有名があります。 重要な手順を次に示します。
図 5: 共有名を実装するプロセス
開発者は、キー ペアとアセンブリのソース ファイルのセットを渡すコンパイラを呼び出します。 キー ペアは、SN という SDK ツールを使用して生成されます。 たとえば、次のコマンドは新しいキー ペアを生成し、ファイルに保存します。
Sn –k MyKey.snk The key pair is passed to the compiler using the custom attribute System.Reflection.AssemblyKeyFileAttribute as follows: <assembly:AssemblyKeyFileAttribute("TestKey.snk")>
コンパイラがアセンブリを出力すると、公開キーがアセンブリの ID の一部としてマニフェストに記録されます。 公開キーを ID の一部として含めることは、アセンブリにグローバルに一意の名前を付けるものです。
アセンブリが出力されると、マニフェストを含むファイルは秘密キーで署名されます。 結果の署名は ファイルに格納されます。
Main がコンパイラによって生成されると、MyLib の公開キーは、MyLib への参照の一部として Main のマニフェストに格納されます。
実行時には、厳密な名前が開発者に必要な利点を与えることを確認するために、.NET Frameworkが実行する 2 つの手順があります。 まず、MyLib の厳密な名前署名は、アセンブリがグローバル アセンブリ キャッシュにインストールされている場合にのみ検証されます。ファイルがアプリケーションによって読み込まれると、署名は再度検証されません。 共有アセンブリがグローバル アセンブリ キャッシュに展開されていない場合、ファイルが読み込まれるたびに署名が検証されます。 署名を確認すると、アセンブリのビルド後に MyLib の内容が変更されていないことが確認されます。 2 番目の手順では、Main の MyLib への参照の一部として格納されている公開キーが、MyLib の ID の一部である公開キーと一致することを確認します。 これらのキーが同一の場合、Main の作成者は、読み込まれた MyLib のバージョンが、Main がビルドされた MyLib のバージョンを作成したのと同じ発行元から取得されていることを確認できます。 このキーの等価性チェックは、Main から MyLib への参照が解決されるときに実行時に行われます。
"署名" という用語は、多くの場合、Microsoft Authenticode® を思い浮かべられます。 厳密な名前と Authenticode は何の関係もないことを理解することが重要です。 2 つの手法の目標は異なります。 特に、Authenticode は発行元に関連付けられた信頼のレベルを意味しますが、厳密な名前は関連付けされません。 厳密な名前に関連付けられている証明書やサードパーティの署名機関はありません。 また、厳密な名前の署名は、ビルド プロセスの一部としてコンパイラ自体によって行われることがよくあります。
注目すべきもう 1 つの考慮事項は、"遅延署名" プロセスです。 アセンブリの作成者が、完全な署名を行うために必要な秘密キーにアクセスできないことがよくあります。 ほとんどの企業は、少数のユーザーのみがアクセスできる適切に保護されたストアにこれらのキーを保持しています。 その結果、.NET Frameworkは"遅延署名" と呼ばれる手法を提供します。これにより、開発者は公開キーのみを使用してアセンブリをビルドできます。このモードでは、秘密キーが指定されていないため、ファイルは実際には署名されません。 代わりに、ファイルは後で SN ユーティリティを使用して署名されます。 遅延署名の使用方法の詳細については、「.NET Framework SDK でのアセンブリの遅延署名」を参照してください。
バージョン ポリシー
前述のように、各アセンブリ マニフェストには、ビルドされた各依存関係のバージョンに関する情報が記録されます。 ただし、アプリケーションの作成者または管理者が実行時に別のバージョンの依存関係を使用して実行するシナリオがいくつかあります。 たとえば、管理者は、修正プログラムを取得するためにすべてのアプリケーションを再コンパイルする必要なく、バグ修正リリースを展開できる必要があります。 また、管理者は、セキュリティ ホールやその他の重大なバグが見つかった場合に、アセンブリの特定のバージョンを使用できないように指定できる必要があります。 .NET Frameworkを使用すると、バージョン ポリシーを使用したバージョン バインディングでこの柔軟性を実現できます。
アセンブリのバージョン番号
各アセンブリには、ID の一部として 4 部構成のバージョン番号があります (つまり、一部のアセンブリのバージョン 1.0.0.0 とバージョン 2.1.0.2 は、クラス ローダーに関する限り、完全に異なる ID です)。 ID の一部としてバージョンを含めることは、サイド バイ サイドの目的でアセンブリのさまざまなバージョンを区別するために不可欠です。
バージョン番号の部分は、メジャー、マイナー、ビルド、リビジョンです。 バージョン番号の部分に適用されるセマンティクスはありません。 つまり、CLR は、バージョン番号の割り当て方法に基づいて、アセンブリの互換性やその他の特性を推論しません。 開発者は、この数値の任意の部分を自由に変更できます。 バージョン番号の形式にセマンティクスは適用されませんが、個々の組織では、バージョン番号の変更方法に関する規則を確立すると役立つ場合があります。 これにより、organization全体の一貫性を維持し、特定のアセンブリのビルド元などを簡単に判断できます。 一般的な規則の 1 つは次のとおりです。
メジャーまたはマイナー。 バージョン番号のメジャー部分またはマイナー部分に対する変更は、互換性のない変更を示します。 この規則では、バージョン 2.0.0.0 はバージョン 1.0.0.0 と互換性がないとみなされます。 互換性のない変更の例としては、一部のメソッド パラメーターの型の変更や、型またはメソッドの完全な削除があります。
ビルド ビルド番号は、通常、毎日のビルドまたは互換性のある小さいリリースを区別するために使用されます。
リビジョン。 リビジョン番号の変更は、通常、特定のバグを修正するために必要な増分ビルド用に予約されています。 特定のバグに対する修正が顧客に出荷されると、リビジョンが頻繁に変更されるという点で、これが "緊急バグ修正" と呼ばれるという音声が聞こえる場合があります。
既定のバージョン ポリシー
共有アセンブリへの参照を解決する場合、CLR は、コードでそのアセンブリへの参照に遭遇したときに読み込む依存関係のバージョンを決定します。 .NET の既定のバージョン ポリシーは非常に簡単です。 参照を解決すると、CLR は呼び出し元アセンブリのマニフェストからバージョンを取得し、まったく同じバージョン番号を持つ依存関係のバージョンを読み込みます。 このようにして、呼び出し元は、ビルドおよびテストされた正確なバージョンを取得します。 この既定のポリシーには、既存のアプリケーションが依存する新しいバージョンの共有アセンブリが別のアプリケーションによってインストールされるシナリオからアプリケーションを保護する プロパティがあります。 .NET より前の既存のアプリケーションでは、既定で新しい共有コンポーネントの使用が開始されることを思い出してください。 ただし、.NET では、共有アセンブリの新しいバージョンをインストールしても、既存のアプリケーションには影響しません。
カスタム バージョン ポリシー
アプリケーションが出荷された正確なバージョンにバインドすると、目的のバージョンではない場合があります。 たとえば、管理者が共有アセンブリに重大なバグ修正プログラムを展開し、ビルドされたバージョンに関係なく、すべてのアプリケーションでこの新しいバージョンを使用するようにする場合があります。 また、共有アセンブリのベンダーが既存のアセンブリにサービス リリースを出荷している可能性があり、すべてのアプリケーションが元のバージョンではなくサービス リリースの使用を開始することを望んでいます。 これらのシナリオやその他のシナリオは、バージョン ポリシーを使用して.NET Frameworkでサポートされています。
バージョン ポリシーは XML ファイルに記載されており、単に別のバージョンではなくアセンブリの 1 つのバージョンを読み込む要求です。 たとえば、次のバージョン ポリシーでは、MarineCtrl というアセンブリのバージョン 5.0.0.0 ではなくバージョン 5.0.0.1 を読み込むよう CLR に指示します。
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly
<assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
<bindingRedirect oldVersion="5.0.0.0" newVersion="5.0.0.1" />
</dependentAssembly>
</assemblyBinding>
特定のバージョン番号から別のバージョンにリダイレクトするだけでなく、さまざまなバージョンから別のバージョンにリダイレクトすることもできます。 たとえば、次のポリシーは、MarineCtrl の 0.0.0.0 から 5.0.0.0 までのすべてのバージョンをバージョン 5.0.0.1 にリダイレクトします。
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly
<assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.1" />
</dependentAssembly>
</assemblyBinding>
バージョン ポリシー レベル
.NET で適用できるバージョン ポリシーには、アプリケーション固有のポリシー、発行元ポリシー、コンピューター全体のポリシーの 3 つのレベルがあります。
アプリケーション固有のポリシー。 各アプリケーションには、依存アセンブリの異なるバージョンにバインドするアプリケーションの要望を指定できるオプションの構成ファイルがあります。 構成ファイルの名前は、アプリケーションの種類によって異なります。 実行可能ファイルの場合、構成ファイルの名前は実行可能ファイルの名前 + ".config" 拡張子です。 たとえば、"myapp.exe" の構成ファイルは "myapp.exe.config" になります。 ASP.NET アプリケーションの構成ファイルは常に "web.config" です。
パブリッシャー ポリシー。 アプリケーション固有のポリシーはアプリケーション開発者または管理者によって設定されますが、発行者ポリシーは共有アセンブリのベンダーによって設定されます。 発行元ポリシーは、アセンブリのさまざまなバージョンに関するベンダーの互換性に関する声明です。 たとえば、共有Windows フォーム コントロールのベンダーが、コントロールに対して多数のバグ修正を含むサービス リリースを提供しているとします。 元のコントロールはバージョン 2.0.0.0 で、サービス リリースのバージョンは 2.0.0.1 です。 新しいリリースにはバグ修正 (破壊的な API の変更なし) が含まれているだけなので、コントロール ベンダーは、新しいリリースで発行元ポリシーを発行する可能性があります。これにより、2.0.0.0 を使用した既存のアプリケーションが 2.0.0.1 の使用を開始します。 発行元ポリシーは、アプリケーションおよびコンピューター全体のポリシーと同様に XML で表現されますが、他のポリシー レベルとは異なり、発行者ポリシーはアセンブリ自体として配布されます。 この主な理由は、特定のアセンブリのポリシーを解放するorganizationが、アセンブリ自体を解放したorganizationと同じになるようにするためです。 これは、元のアセンブリとポリシー アセンブリの両方に、同じキー ペアを持つ厳密な名前を付ける必要があります。
マシン全体のポリシー。 最終的なポリシー レベルは、コンピューター全体のポリシー (管理者ポリシーとも呼ばれることもあります) です。 マシン全体のポリシーは、.NET Frameworkインストール ディレクトリの "config" サブディレクトリにあるmachine.configに格納されます。 インストール ディレクトリは %windir%\microsoft.net\framework\%runtimeversion% です。 machine.configで作成されたポリシー ステートメントは、マシン上で実行されているすべてのアプリケーションに影響します。 マシン全体のポリシーは、特定のコンピューター上のすべてのアプリケーションでアセンブリの特定のバージョンを強制的に使用するために管理者によって使用されます。 これが使用される最もコメント シナリオは、セキュリティまたはその他の重大なバグ修正がグローバル アセンブリ キャッシュにデプロイされている場合です。 固定アセンブリを展開した後、管理者はマシン全体のバージョン ポリシーを使用して、アプリケーションが古い壊れたバージョンのアセンブリを使用しないようにします。
ポリシーの評価
厳密に名前付きのアセンブリにバインドするときに CLR が最初に行うことは、バインド先のアセンブリのバージョンを決定することです。 このプロセスは、まず、参照を行うアセンブリのマニフェストに記録された目的のアセンブリのバージョン番号を読み取ります。 その後、ポリシーが評価され、いずれかのポリシー レベルに別のバージョンへのリダイレクトが含まれているかどうかを判断します。 ポリシー レベルは、アプリケーション ポリシーから順に評価され、その後にパブリッシャーと最後に管理者が続きます。
任意のレベルで見つかったリダイレクトは、前のレベルによって行われたステートメントをオーバーライドします。 たとえば、アセンブリ A がアセンブリ B を参照しているとします。A のマニフェストの B への参照は、バージョン 1.0.0.0 です。 さらに、アセンブリ B に付属するパブリッシャー ポリシーは、参照を 1.0.0.0 から 2.0.0.0 にリダイレクトします。 さらに、バージョン ポリシーには、バージョン 3.0.0.0 への参照を指示するマシン全体の構成ファイルがあります。 この場合、コンピューター レベルで行われたステートメントは、パブリッシャー レベルで行われたステートメントをオーバーライドします。
パブリッシャー ポリシーのバイパス
3 種類のポリシーが適用される順序により、発行元のバージョン リダイレクト (パブリッシャー ポリシー) は、呼び出し元アセンブリのメタデータに記録されているバージョンと、設定されたアプリケーション固有のポリシーの両方をオーバーライドできます。 ただし、バージョン管理に関する発行元の推奨事項を常に受け入れるようにアプリケーションを強制すると、DLL Hell に戻る可能性があります。 結局のところ、DLL Hell は主に、共有コンポーネントで下位互換性を維持するのが難しいことが原因です。 新しいバージョンの共有コンポーネントのインストールによってアプリケーションが破損するシナリオをさらに回避するために、.NET のバージョン ポリシー システムでは、個々のアプリケーションで発行元ポリシーをバイパスできます。 言い換えると、アプリケーションは、使用するバージョンに関する発行元の推奨事項を受け取ることを拒否できます。 アプリケーションは、アプリケーション構成ファイルの "publisherPolicy" 要素を使用して発行元ポリシーをバイパスできます。
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<publisherPolicy apply="no"/>
</assemblyBinding>
.NET 構成ツールを使用したバージョン ポリシーの設定
幸いにも、.NET Frameworkにはグラフィカル管理ツールが付属しているため、XML ポリシー ファイルの手動編集について心配する必要はありません。 このツールでは、アプリケーションとマシン全体の両方のバージョン ポリシーがサポートされています。 ツールは、コントロール パネルの [管理ツール] セクションにあります。 管理ツールの最初の画面は、図 6 のようになります。
図 6: 管理 ツール
次の手順では、アプリケーション固有のポリシーを設定する方法について説明します。
ツリー ビューの [アプリケーション] ノードにアプリケーションを追加します。 [アプリケーション] ノードを右クリックし、[ 追加] をクリックします。 [ 追加] ダイアログ ボックスには、選択する .NET アプリケーションの一覧が表示されます。 アプリケーションが一覧にない場合は、[ その他] をクリックしてアプリケーションを追加できます。
ポリシーを設定するアセンブリを選択します。 [構成済みアセンブリ] ノードを右クリックし、[ 追加] をクリックします。 オプションの 1 つは、アプリケーションが参照するアセンブリの一覧からアセンブリを選択することです。図 7 に示すように、次のダイアログが表示されます。 アセンブリを選択し、[ 選択] をクリックします。
図 7: アセンブリの選択
[ プロパティ ] ダイアログ ボックスで、バージョン ポリシー情報を入力します。 [ バインド ポリシー ] タブをクリックし、図 8 に示すように、テーブルに目的のバージョン番号を入力します。
図 8: [バインド ポリシー] タブ
デプロイ
展開には、コードのパッケージ化と、アプリケーションが実行されるさまざまなクライアントとサーバーへのパッケージの配布という、少なくとも 2 つの異なる側面が含まれます。 .NET Frameworkの主な目的は、ゼロインパクトのインストールと xcopy デプロイを実現できるようにすることで、デプロイ (特にディストリビューションの側面) を簡素化することです。 アセンブリの自己記述性により、レジストリへの依存関係を削除できるため、インストール、アンインストール、レプリケーションがはるかに簡単になります。 ただし、xcopy が配布メカニズムとして十分または適切でないシナリオがあります。 このような場合、.NET Frameworkは広範なコード ダウンロード サービスと Windows インストーラーとの統合を提供します。
パッケージ
.NET Frameworkの最初のリリースでは、次の 3 つのパッケージ化オプションを使用できます。
- As-built (DLL と EXEs)。 多くのシナリオでは、特別なパッケージ化は必要ありません。 アプリケーションは、開発ツールによって生成された形式でデプロイできます。 つまり、DLL と EXEs のコレクションです。
- Cab ファイル。 Cab ファイルは、より効率的なダウンロードのためにアプリケーションを圧縮するために使用できます。
- Windows インストーラー パッケージ。 Microsoft Visual Studio .NET やその他のインストール ツールを使用すると、Windows インストーラー パッケージ (.msi ファイル) をビルドできます。 Windows インストーラーを使用すると、アプリケーションの修復、オンデマンド インストール、およびその他の Microsoft Windows アプリケーション管理機能を利用できます。
配布シナリオ
.NET アプリケーションは、xcopy、コードダウンロード、Windows インストーラーなど、さまざまな方法で配布できます。
Web アプリケーションや Web サービスを含む多くのアプリケーションでは、一連のファイルをディスクにコピーして実行するのと同じくらい簡単です。 アンインストールとレプリケーションも同じくらい簡単です。ファイルを削除するか、コピーするだけです。
.NET Frameworkでは、Web ブラウザーを使用した広範なコード ダウンロード のサポートが提供されます。 この領域では、次のようないくつかの改善が行われています。
- 影響ゼロ。 マシンにレジストリ エントリは作成されません。
- 増分ダウンロード。 アセンブリの一部は、参照されている場合にのみダウンロードされます。
- 分離されたアプリケーションにダウンロードします。 1 つのアプリケーションの代わりにダウンロードされたコードは、マシン上の他のアプリケーションに影響を与えることはできません。 コードダウンロードのサポートの主な目的は、ユーザーが特定の Web サイトを参照し、その新しいバージョンが他のアプリケーションに悪影響を与える副作用として、一部の共有コンポーネントの新しいバージョンをダウンロードするシナリオを防ぐことです。
- Authenticode ダイアログはありません。 コード アクセス セキュリティ システムは、モバイル コードを部分的な信頼レベルで実行できるようにするために使用されます。 ユーザーには、コードを信頼するかどうかの決定を求めるダイアログ ボックスが表示されることはありません。
最後に、.NET は Windows インストーラーと Windows のアプリケーション管理機能と完全に統合されています。
まとめ
.NET Frameworkでは、影響を受け取らないインストールが可能になり、DLL Hell に対処します。 アセンブリは、これらの機能を有効にするために使用される自己記述型のバージョン管理可能な展開ユニットです。
分離されたアプリケーションを作成する機能は、他のアプリケーションによってシステムに加えられた変更の影響を受けないアプリケーションをビルドできるため、非常に重要です。 .NET Frameworkでは、アプリケーションのディレクトリ構造内に配置されるアプリケーション プライベート アセンブリを使用して、この種類のアプリケーションを推奨します。
サイド バイ サイドは、.NET での共有とバージョン管理のストーリーの中核部分です。 サイド バイ サイドでは、複数のバージョンのアセンブリをマシンに同時にインストールして実行し、各アプリケーションがそのアセンブリの特定のバージョンを要求できるようにします。
CLR は、アプリケーションの一部間のバージョン情報を記録し、実行時にその情報を使用して、依存関係の適切なバージョンが読み込まれるようにします。 バージョン ポリシーは、アプリケーション開発者と管理者の両方で使用して、読み込まれる特定のアセンブリのバージョンを柔軟に選択できます。
windows インストーラー、コードのダウンロード、簡単な xcopy など、.NET Frameworkによって提供されるパッケージ化と配布のオプションがいくつかあります。