Steven Pratschner
Microsoft Corporation
September 2000
日本語版最終更新日 2000 年 11 月 30 日
要約: この記事では、アセンブリの概念を紹介し、.NET Framework がアセンブリを使ってバージョンニングと導入の問題をどのように解決しているかを説明します。
目次
はじめに
問題の解説
ソリューションの特性
アセンブリ: 基本構成要素
バージョンニングと共有
バージョン ポリシー
導入
要約
はじめに
Microsoft® .NET Framework では、アプリケーションの導入の単純化と DLL Hell の解決を目的とするいくつかの新機能が追加されています。エンド ユーザーと開発者は、今日のコンポーネント ベースのシステムで生じるバージョンニングと導入の問題をよく知っています。たとえば、ほぼすべてのエンド ユーザーが、マシンに新しいアプリケーションをインストールすると、既存のアプリケーションがなぜか動作しなくなったという経験をしています。また、大部分の開発者は、COM クラスをアクティブ化するために、必要なすべてのレジストリ項目に一貫性を持たせようと、Regedit を使って奮闘した経験を持っています。
.NET Framework で、DLL Hell を解消するために使用されるデザイン ガイドラインとインプリメンテーション テクニックは、Microsoft Windows® 2000 で行われた作業をベースにしています。これについては、Rick Anderson が「The End of DLL Hell (英語)」で、またDavid D'Souza、BJ Whalen、およびPeter Wilsonが「アプリケーションで共有する Side-by-Side コンポーネントの実装 (拡張)」で解説しているので参照してください。.NET Frameworkは、この2つの記事で説明されている機能の多く (アプリケーションの隔離とサイド バイ サイド コンポーネントを含む) を、.NETプラットフォーム上で構築されるアプリケーションに対して提供しています。また、将来的には、.NETプラットフォーム上でも、ネイティブなWindowsアプリケーションに対して提供されたバージョンニング サポートが実現される予定です。
この記事では、アセンブリの概念を紹介し、.NETがアセンブリを使ってバージョンニングと導入の問題をどのように解決しているかを説明します。特に、アセンブリの構造、その名前の付け方、および、コンパイラとCommon Language Runtimeがアセンブリを使ってアプリケーションの個々の部分の間のバージョン依存関係をどのように記録し、実施しているかを説明します。また、アプリケーションと開発者が、バージョン ポリシーと呼ばれるものを使って、バージョンニングの動作をどのようにカスタマイズするかという点についても説明します。
アセンブリの紹介と説明を終えたら、いくつかの導入シナリオによって、.NET Frameworkで利用できるさまざまなパッケージングと配布のオプションの例を示します。
問題の解説
バージョンニング
カスタマから見ると、バージョンニングの最も一般的な問題は、DLL Hellと呼ばれる状況です。簡単に述べると、DLL Hellは、複数のアプリケーションがダイナミック リンク ライブラリ (DLL) やComponent Object Model (COM) クラスなどの共通コンポーネントを共有しようと試みたときに発生する一連の問題です。典型的なケースでは、あるアプリケーションが、マシン上にすでに存在するバージョンとの後方互換性がない、共有コンポーネントの新しいバージョンをインストールしたときに起こります。新たにインストールしたアプリケーションは正常に動作しますが、前のバージョンの共有コンポーネントに依存していた既存のアプリケーションは動作しなくなる可能性があります。ケースによっては、もっと微妙な条件が問題の原因となることもあります。たとえば、ユーザーが、何らかのWebサイトを訪問した副作用として、Microsoft ActiveX® コントロールをダウンロードしたとしましょう。コントロールがダウンロードされると、マシン上に存在していたコントロールの既存のバージョンは置き換えられます。マシン上にインストールされていた何らかのアプリケーションが、たまたまこのコントロールを使用していた場合には、このアプリケーションも動作しなくなる可能性があります。
多くのケースでは、アプリケーションが動作しなくなったことをユーザーが発見するまでにかなりの時間がかかります。その結果、マシンに加えたどの変更がアプリケーションに影響を与えたのかが思い出せなくなることがあります。ユーザーが1週間前に何かをインストールしたことを覚えていたとしても、そのインストールと、いま生じている動作の間に明らかな関連はありません。さらに事態を悪くするのは、ユーザー (またはその人を助けようとしているサポート要員) が問題の原因を判断するために使える診断ツールが、現時点ではほとんど存在しないということです。
このような問題が生じるのは、アプリケーションの各種のコンポーネントにバージョン情報が、システム上で記録も管理もされていないことによります。また、1つのアプリケーションのためにシステムに加えられた変更は、一般にマシン上のすべてのアプリケーションに影響を与えます。今日では、変更から完全に隔離されたアプリケーションを構築するのは容易なことではありません。
隔離されたアプリケーションを構築するのが難しい理由の1つは、現在のランタイム環境では、一般にコンポーネントまたはアプリケーションのバージョンを1つしかインストールできないようになっていることにあります。つまり、コンポーネントの作者は、後方互換性が保たれるようにコードを書かないと、新しいコンポーネントをインストールしたときに既存のアプリケーションが動作しなくなる可能性があります。しかし現実の世界では、つねに後方互換性を保ち続けるコードを書くのは、不可能ではなくてもきわめて困難です。.NETにおけるバージョンニングの中核には、サイド バイ サイドの概念があります。サイド バイ サイドとは、マシン上に同じコンポーネントの複数のバージョンを同時にインストールし、実行できる能力のことです。サイド バイ サイドをサポートするコンポーネントでは、個々のアプリケーションは共有コンポーネントの異なるバージョンを自由に使うことができるため、開発者は厳密な後方互換性に縛られる必要はありません。
導入とインストール
今日のアプリケーションのインストールは複数のステップから成るプロセスです。一般に、アプリケーションをインストールする際には、多数のソフトウェア コンポーネントをディスクにコピーし、これらのコンポーネントをシステムに登録する一連のレジストリ項目を作成します。
レジストリの項目とディスク上のファイルが分離されているため、アプリケーションの複製やアンインストールがきわめて難しくなります。また、レジストリ内でCOMクラスを完全に記述するために必要な各種の項目の整合性の関係はきわめて緩いものです。これらの項目には、ドキュメントの拡張子やコンポーネントのカテゴリを登録するためのものだけでなく、コクラス、インターフェイス、タイプ ライブラリ、およびDCOMアプリケーションIDなどの項目も含まれます。これらの項目を手動で同期させなくてはならないことも珍しくありません。
最後に、これらのレジストリ項目は、COMクラスをアクティブ化するためにも必要となるものです。個々のクライアント マシンに適切なレジストリ項目を追加しなくてはならないため、分散アプリケーションの導入プロセスが大幅に複雑になります。
今日のもう1つの一般的な問題は、実行中のアプリケーションをアップデートするのが難しいことです。これはWebアプリケーションの場合に特に問題となります。アプリケーションが使用するCOMクラスをアップデートするために、Web Serviceをいったん停止し、再起動しなくてはならないからです。
これらの問題は、主にコンポーネントの記述がコンポーネントそのものから分離された状態で保持されていることによって引き起こされています。言い換えると、アプリケーションは自己記述的でも自己完結的でもありません。
ソリューションの特性
.NET Frameworkは、上記の問題を解決するために、以下の基本的な能力を提供しなくてはなりません。
-
アプリケーションは自己記述的でなくてはならない - 自己記述的なアプリケーションでは、レジストリに対する依存性が解消されるため、インストールの影響をゼロに抑え、アンインストールと複製を単純化することができます。
-
バージョン情報を記録し、管理しなくてはならない - プラットフォームにバージョンニング サポートを組み込み、適切なバージョンの依存ファイルが実行時にロードされるようにする必要があります。
-
「最後に正常に動作した構成」を記録しておかなくてはならない - アプリケーションの実行が成功したとき、プラットフォームは、正常に動作したコンポーネントのセットを、それらのバージョンも含めて記録できなくてはなりません。
-
サイド バイ サイド コンポーネントをサポートしなくてはならない - コンポーネントの複数のバージョンをマシンにインストールし、同時に実行できるようにすることで、呼び出し側は特定のバージョンを「強制的」に使わされるのではなく、自分がロードしたいバージョンを指定できるようになります。.NET Frameworkでは、このサイド バイ サイドの概念をさらに一歩進めて、フレームワークそのものの複数のバージョンを1台のマシン上で共存できるようにしています。これにより、管理者は必要ならば.NET Frameworkのバージョンに応じて異なるアプリケーションを実行できるため、アップグレードの作業がさらに単純化されます。
-
アプリケーションの隔離が可能でなくてはならない - .NET Frameworkは、他のアプリケーションがマシンに加えた変更の影響を受けないようなアプリケーションを簡単に作成できなくてはならず、むしろそのようなアプリケーションがデフォルトで作成されるようになっている必要があります。
アセンブリ: 基本構成要素
アセンブリは、.NET Frameworkがこれまでに述べたバージョンニングと導入の問題を解決するために使用する基本構成要素です。アセンブリは、タイプとリソースのための導入単位です。多くの点で、アセンブリは今日のDLLに対応しています。実際、アセンブリは「論理的なDLL」と考えることができます。
アセンブリは、マニフェストと呼ばれるメタデータを通して自己記述されます。.NETは、タイプを記述するためにメタデータを使用するのと同様に、タイプを含んでいるアセンブリの記述にもメタデータを使用します。
アセンブリは、単なる導入のための道具ではありません。たとえば、.NETにおけるバージョンニングは、アセンブリ レベルで行われます。それよりも小さいモジュールやタイプなどはバージョンニングされません。また、アセンブリはアプリケーション間でのコードの共有にも使用されます。タイプが含まれているアセンブリは、そのタイプのアイデンティティ (識別情報) の一部です。
コード アクセス セキュリティ システムは、その権限モデルのコアの部分でアセンブリを使用しています。アセンブリの作者は、マニフェストの中にコードを実行するために必要な権限のセットを記録し、管理者は、コードがどのアセンブリに含まれているかということに基づいて、コードに権限を与えます。
最後に、アセンブリは、タイプ システムとランタイム システムのコアともなっています。タイプのための可視性境界を確立し、タイプの参照を解決するためのランタイム スコープの役割を果たします。
アセンブリ マニフェスト
マニフェストには、アセンブリに関する以下のデータが含まれています。
-
アイデンティティ - アセンブリのアイデンティティは、名前、バージョン番号、およびオプションのカルチャーの3つの部分から構成されています。
-
ファイル リスト - マニフェストには、アセンブリを構成するすべてのファイルのリストが含まれています。マニフェストは各ファイルについて、その名前と、マニフェストが作成された時点での内容を暗号化したハッシュを記録しています。このハッシュは、導入単位に一貫性があるかどうかを確認するために、実行時に検証されます。
-
参照先のアセンブリ - 呼び出し側アセンブリのマニフェストには、アセンブリ間の依存関係が格納されています。依存関係情報には、実行時に正しいバージョンの依存ファイルをロードするために使用されるバージョン番号が含まれています。
-
エクスポートされたタイプとリソース - タイプとリソースで使用できる可視性オプションには、「自分のアセンブリ内でのみ可視」と、「アセンブリの外部の呼び出し側に対して可視」があります。
-
権限要求 - アセンブリの権限要求は、次の3つのセットにグループ化されています。1) アセンブリを実行するために必要なもの。2) あれば望ましいが、なくてもアセンブリの何らかの機能が保たれるもの。3) 作者がアセンブリに許可したくないと考えたもの。
IL Disassembler (Ildasm) SDKツールは、アセンブリ内のコードとメタデータを調べるのに便利です。図1は、Ildasmでのマニフェストの表示例です。.assemblyディレクティブはアセンブリを識別し、.assembly externディレクティブはこのアセンブリが依存している他のアセンブリに関する情報を含んでいます。
図1. IL Disassemblerが表示するマニフェストの例
アセンブリの構造
これまでは、アセンブリを主に論理的な概念として説明してきました。このセクションでは、アセンブリをより具体的に理解できるように、アセンブリの物理的な構造を説明します。
一般にアセンブリは、アセンブリ メタデータ (マニフェスト)、タイプを記述するメタデータ、タイプをインプリメントする中間言語 (IL) コード、およびリソースのセットの4つの要素から構成されています。これらすべての要素がすべてのアセンブリに存在しているわけではありません。必須なのはマニフェストだけですが、アセンブリが何らかの意味のある機能を持つためには、タイプとリソースのどちらかが必要となります。
この4つの要素を「パッケージ化」する方法には、いくつかのオプションがあります。たとえば、図2はアセンブリ全体、すなわちマニフェスト、タイプ メタデータ、ILコード、およびリソースを含んでいる1つのDLLを示しています。
図2. すべてのアセンブリ要素を含んでいるDLL
また、アセンブリの内容が複数のファイルに分散することもあります。図3では、作者は一部のユーティリティ コードを別のDLLに分離し、大きなリソース ファイル (この例では JPEG) を元のファイルのまま残しておくことに決めました。このようなことを行う理由の1つとしては、コードのダウンロードの最適化があります。.NET Frameworkは、ファイルのダウンロードを、そのファイルが参照している場合にのみ行うので、アセンブリにあまり頻繁にアクセスされないコードやリソースが含まれている場合には、それらを複数のファイルに分割することでダウンロードの効率を高めることができます。
図3. 複数のファイルに分散されたアセンブリ要素
バージョンニングと共有
DLL Hellの主な原因の1つに、現在のコンポーネント ベース システムで使用されている共有モデルがあります。デフォルトでは、個々のソフトウェア コンポーネントは、マシン上の複数のアプリケーションによって共有されます。たとえば、インストール プログラムがシステム ディレクトリにDLLをコピーしたり、COMレジストリにクラスを登録するたびに、そのコードはマシン上で実行される他のアプリケーションに影響を与える可能性があります。特に、既存のアプリケーションがその共有コンポーネントの以前のバージョンを使用していた場合、そのアプリケーションは自動的に新しいバージョンを使い始めることになります。共有コンポーネントが厳密な後方互換性を持っていれば問題は生じませんが、多くのケースでは、後方互換性を保つのは不可能ではないにしても困難です。後方互換性が保たれていない、または保つことができないと、他のアプリケーションがインストールされたことの副作用として、アプリケーションの動作が異常をきたすことがあります。
.NETにおける基本的な設計ガイドラインは、(アセンブリを) 隔離したコンポーネントというものです。アセンブリを隔離するとは、1つのアセンブリは1つのアプリケーションからしかアクセスされないということ、つまりマシン上の複数のアプリケーションで共有されることはなく、他のアプリケーションがシステムに加えた変更の影響を受けないということです。この隔離により、開発者は自分のアプリケーションが使用するコードを完全に制御することができます。隔離されたアセンブリ、あるいはアプリケーション プライベートなアセンブリは、.NETアプリケーションのデフォルトです。隔離されたコンポーネントを利用するトレンドは、Microsoft Windows® 2000で.localファイルが導入されたときに始まりました。このファイルは、OSローダーとCOMに対し、要求されたコンポーネントを探すときに、まずアプリケーション ディレクトリを探す指示に使われます (MSDN Libraryの関連記事「Implementing Side-by-Side Component Sharing in Applications (Expanded) (英語)」を参照してください)。
ただし、アプリケーション間でのアセンブリの共有が必要な場合もあります。たとえば、すべてのアプリケーションにSystem.Winforms、System.ASP、および共有Web フォームコントロールの独自のコピーを持たせることに意味がないことは明らかです。
.NETでは、アプリケーション間のコードの共有は明示的に決定されます。特に、共有されるアセンブリは、同じアセンブリの複数のバージョンを、同じマシン上にインストールし、ときには同じプロセスの中から同時に実行できるように、サイド バイ サイドをサポートしていなくてはなりません。さらに、共有アセンブリでは名前付けの制約が厳しくなります。たとえば、共有されるアセンブリはグローバルに一意な名前を持っていなくてはなりません。
隔離と共有の必要性から、アセンブリには2つの「種類」があると考えることができます。この2つの間には、現実の構造上の違いがあるわけではなく、その使われ方にのみ違いがあります。つまり、アセンブリが1つのアプリケーションに対してプライベートなものなのか、多数のアプリケーションから共有されるのかということです。
アプリケーション プライベートなアセンブリ
アプリケーション プライベートなアセンブリは、1つのアプリケーションからしか見えないアセンブリです。これは、.NETアプリケーションの一般的なケースになると予想されます。.NET Frameworkは、他のアプリケーションがシステムに加える変更から隔離されたアプリケーションの構築を推奨しているためです。
プライベートなアセンブリの名前付けの条件は単純なものです。アセンブリ名は、そのアプリケーション内でのみ一意であればよいということです。グローバルに一意な名前を付ける必要はありません。アプリケーション開発者は、どのアセンブリがアプリケーションに内で隔離されるのかを完全に制御できるので、名前の一意性を保つのは簡単です。
アプリケーション プライベートなアセンブリは、それが使用されるアプリケーションのディレクトリ構造の中に導入されます。プライベート アセンブリは、アプリケーション ディレクトリに直接置くことも、そのサブディレクトリに置くこともできます。Common Language Runtimeはこれらのアセンブリをプロービングと呼ばれるプロセスを通して発見します。プロービングは、アセンブリ名を、マニフェストを含んでいるファイルの名前にマッピングするという単純な操作です。
具体的に説明すると、Common Language Runtimeはアセンブリ参照に記録されているアセンブリの名前を取得し、これに".dll"を追加し、アプリケーション ディレクトリの中でそのファイルを探します。この手法には、Runtimeがアセンブリによって指定されたサブディレクトリ、またはアセンブリのカルチャーによって指定されたサブディレクトリを探すといういくつかのバリエーションがあります。たとえば、開発者はドイツ語にローカライズされたリソースを含んでいるアセンブリを"de"という名前のサブディレクトリに、スペイン語にローカライズされたリソースを含んでいるアセンブリを"es"という名前のサブディレクトリに導入することができます。
前に述べたように、個々のアセンブリ マニフェストは、その依存関係に関するバージョン情報を含んでいます。このバージョン情報はプライベート アセンブリに対しては実施されません。アプリケーション ディレクトリに導入されるアセンブリについては、開発者が完全に制御できるからです。
共有アセンブリ
.NET Frameworkは共有アセンブリという概念もサポートしています。共有アセンブリは、マシン上の複数のアプリケーションから使用されるアセンブリです。.NETでは、アプリケーション間のコードの共有は明示的に決定されます。共有アセンブリには、今日のわれわれが経験している共有から生じる問題を避けるために、いくつかの要件が課せられます。前述のサイド バイ サイドをサポートするという条件に加えて、共有アセンブリの名前付けにははるかに厳しい制限があります。たとえば、共有アセンブリはグローバルに一意な名前を持っていなくてはなりません。また、システムは「名前の保護」を提供しなくてはなりません。つまり、他の誰かがアセンブリの名前を再利用するのを禁止するということです。たとえば、グリッド コントロールのベンダが、アセンブリのバージョン1をリリースした場合、他のベンダが、このグリッド コントロールのバージョン2と称するアセンブリをリリースすることがないという保証が必要となります。.NET Frameworkは、共有名 (次のセクションで詳しく説明します) というテクニックを使って、これらの名前付けの要件をサポートしています。
一般に、アプリケーション作者は、アプリケーションが使用する共有アセンブリに対して細かい制御をすることができません。このため、バージョン情報は共有アセンブリに対する参照が行われるたびにチェックされます。さらに、.NET Frameworkでは、アプリケーションと管理者は、バージョン ポリシーを指定することで、アプリケーションが使用するアセンブリのバージョンをオーバーライドすることができます。
通常、共有アセンブリはグローバル アセンブリ ストアに導入されます。グローバル アセンブリ ストアは、複数のアプリケーションから使用される、マシン全体のアセンブリのストアです。このストアの使用は必須ではありませんが、これを使用することでいくつかの利点を享受することができます。たとえば、アセンブリの複数のバージョンのサイド バイ サイド ストレージが自動的に提供されます。また、管理者はこのストアを使って、マシン上のすべてのアプリケーションに使用させたいバグ フィックスやセキュリティ パッチを導入することができます。この点で、アセンブリをグローバル アセンブリ ストアに導入すれば、マシン上の複数のアプリケーションに影響を及ぼすことができます。.NET Frameworkは、今日のシステムの%windir%\system32などの共有エリアで生じる問題を避けるために、(後に説明する) バージョン ポリシーの概念を使用します。
アセンブリをストアに追加するためには、管理者による明示的なアクションが必要となります。実際、インストールを行うプロセスは「管理者権限」を持っていなくてはなりません。アセンブリは、アプリケーションを実行した副作用としてストアに格納されることはなく、また現時点では共有アセンブリのキャッシングも行われません。Visual Studo.NETがリリースされるまでに、Windows Installerはアセンブリとアセンブリ ストアをネイティブで認識するようにアップグレードされる予定です。つまり、.NETアプリケーションでも、オンデマンド インストールやアプリケーション修復といったWindows Installerのすべての機能を利用できるようになります。
.NET SDKには、アセンブリ ストアを扱うための2つのツールが付属しています。第1のツールはALという名前を持ち、アセンブリをストアに追加することができます。ALは、ストアにアセンブリを追加するために完全なWindows Installerパッケージを作成するのが面倒な、開発およびテストのシナリオで便利です。ストアにアセンブリを追加するには、/installスイッチを使用します。
Al /install:myassembly.dll
第2のツールは、Windowsエクスプローラを使ってストアを操作できるようにするWindows Shell Extensionです。図4は、グローバル アセンブリ ストアを表示した様子を示しています。
図4. グローバル アセンブリ ストア
共有名
共有名は、共有アセンブリに関連する厳密な名前付けの要件を使用可能にするために使われます。共有名には次の3つの目標があります。
-
名前の一意性 - 共有アセンブリは、グローバルに一意な名前を持たなくてはなりません。
-
名前の偽造の防止 - 開発者は、他の人が意図して、または誤って、自分のアセンブリの新バージョンをリリースして偽造することがないという保証を望んでいます。
-
参照時のアイデンティティの提供 - 参照をアセンブリに解決する際に、共有名は、ロードされたアセンブリが目的のパブリシャを出所としていることを保証するために使用されます。
共有名は標準的なパブリック キー暗号を使ってインプリメントされています。一般に、このプロセスは次のように動作します。アセンブリの作者は、キーのペアを生成し (または既存のペアを使用し)、マニフェストを含んでいるファイルにプライベート キーで署名し、パブリック キーを呼び出し側に提供します。アセンブリに対する参照が行われるとき、呼び出し側は共有名の生成に使用されたプライベート キーに対応するパブリック キーを記録します。図5は、キーのメタデータへの格納とシグニチャの生成を含めて、開発時のプロセスを示しています。
このシナリオでは、"Main"という名前のアセンブリが、"MyLib"という名前のアセンブリを参照しています。MyLibは共有名を持っています。以下に重要なステップを示します。
図5. 共有名をインプリメントするためのプロセス
- 開発者は、コンパイラを起動し、キーのペアと、アセンブリのソース ファイルのセットを渡します。キーのペアは、一般にSNというSDKツールを使って生成されます。たとえば、次のコマンドは新しいキーのペアを生成し、ファイル (MyKey.snk) に保存します。
ほとんどのコンパイラは、コンパイルのステップの一環として、アセンブリに署名を行います。次に、キーのペアを受け取って、アセンブリに署名を行うC#のコマンド ラインの例を示します。
Csc /t:library math.cs /a.keyfile:MyKey.snk /a.version:1.0.0.0
- コンパイラがアセンブリを出力する際に、パブリック キーはマニフェストの中に、アセンブリのアイデンティティの一部として記録されます。パブリック キーをアイデンティティの一部とすることによって、アセンブリにグローバルに一意な名前が与えられます。
- アセンブリが出力される際に、マニフェストを含むファイルに、プライベート キーを使って署名が行われます。結果として得られたシグニチャはファイルに格納されます。
- コンパイラがMainを生成する際に、MyLibのパブリック キーが、MyLibへの参照の一部として、Mainのマニフェストに格納されます。
実行時に、.NET Frameworkは、共有名が開発者に対してその利点を提供できるように、2つのステップを実行します。第1に、アセンブリがグローバル アセンブリ ストアにインストールされる際に、MyLibの共有名シグニチャが検証されます (ストアに導入されないシグニチャを検証するオプションも用意されています)。シグニチャを検証することで、MyLibの内容が、アセンブリが作成されたときから変更されていないことが保証されます。第2のステップでは、MainのMyLibへの参照の一部として格納されているパブリック キーが、MyLibのアイデンティティの一部であるパブリック キーにマッチしていることが確認されます。この2つのキーが同一のものであれば、Mainの作者は、ロードされたMyLibのバージョンが、Mainの作成で使用されたバージョンのMyLibと同じものであることを確信することができます。このキー等値チェックは、実行時にMainからMyLibへの参照が解決されるときに行われます。
「署名」という言葉は、Microsoft Authenticode® を思い出させます。しかし、共有名とAuthenticodeにはいかなる関連もありません。この2つのテクニックは、それぞれ異なる目標を持っています。特に、Authenticodeはパブリシャに対する一定レベルの信頼関係を意味するのに対し、共有名はそのような意味を持っていません。共有名には、証明やサードパーティの署名機関は関係していません。また、共有名の署名は、一般にコンパイラ自身によって、ビルド プロセスの一環として行われます。ただし、SDKにも署名を行うためのユーティリティが用意されています。
もう1つの注意すべき点は、「テスト署名」プロセスです。アセンブリの作者が、完全な署名を行うために必要なプライベート キーにアクセスできないということはよく起こります。ほとんどの会社は、これらのキーを、少数の人しかアクセスできない保護されたストアに保持しているからです。このため、.NET Frameworkは開発中の「テスト署名」を行うためのテクニックをいくつか用意しています。この場合、「本当の署名」は後から行われます。
バージョン ポリシー
前述のように、個々のアセンブリ マニフェストは、作成に使われた個々の依存ファイルのバージョンに関する情報を記録しています。ただし、アプリケーション作者または管理者が、実行時に異なるバージョンの依存ファイルを実行したいと考えるシナリオもいくつか考えられます。たとえば、管理者は、すべてのアプリケーションを再コンパイルしなくても、バグ フィックス リリースを導入できなくてはなりません。また、管理者は、セキュリティ ホールやその他の重大なバグが発見されたときに、アセンブリの特定のバージョンを使用しないように指定できなくてはなりません。.NET Frameworkでは、バージョン ポリシーによる柔軟なバージョン バインディングを可能にしています。
アセンブリ バージョン番号
個々のアセンブリは、そのアイデンティティの一部として、4つのパートから成るバージョン番号を持っています (つまり、クラス ローダーに関する限り、あるアセンブリのバージョン1.0.0.0とバージョン2.1.0.2はまったく別のアイデンティティです)。バージョンをアイデンティティの一部として取り込むことは、アセンブリの異なるバージョンをサイド バイ サイドの目的に区別する上で必須です。
開発者と管理者の両方が、バージョン番号の構造を理解しておく必要があります。Common Language Runtimeがアセンブリ間のバージョン依存関係を管理する上での鍵となる概念だからです。
図6. アセンブリ バージョン番号の4つのパート
バージョン番号のパートは、メジャー、マイナー、ビルド、およびリビジョンです。メジャーまたはマイナー番号の変更は、互換性のない変更と見なされます。たとえば、開発者が何らかのメソッド パラメータを変更した、または何らかのタイプを完全に削除したなどがこれにあたります。クラス ローダーは、この情報を使用し、依存アセンブリの互換性のないバージョンがデフォルトでは決してロードされないようにしています。
一方、バージョン番号のビルドとリビジョンの部分のみの変更は、互換性がある変更と見なされます。これらの変更は、呼び出し側に異常動作を起こさせないという意味でタイプ定義に互換性があるバグ フィックスやセキュリティ パッチなどです。これらの互換性のある変更は、Quick Fix Engineering (QFE) フィックスやホットフィックスなどと呼ばれることがあります。
デフォルト バージョン ポリシー
Common Language Runtimeは、コード内のアセンブリへの参照を発見すると、依存アセンブリのどのバージョンをロードするのかを決定します。デフォルトでは、参照を解決するためにロードされるアセンブリは、参照に記録されたのと同じメジャーおよびマイナー番号を持っていなくてはなりません。これらの番号が異なるアセンブリには互換性はなく、デフォルトではロードされません。一方、Common Language Runtimeは、QFE (またはホットフィックス) については最も大きい番号を持つものを選択します。
たとえば、MainがMyLibのバージョン1.0.0.0を使ってビルドされていたが、実行時にMyLibのバージョン1.0.1.1が発見された場合には、1.0.1.1がロードされます。
このように、つねに最新のビルドとリビジョンを選択するというポリシーは、「自動QFEポリシー」と呼ばれます。このポリシーの主な目的は、管理者が、すべてのアプリケーションをリビルドしなくてもバグ フィックス リリースを導入できるようにすることです。
カスタム バージョン ポリシー
上記のデフォルト ポリシーが目的の動作にならない場合もあります。たとえば、インストールされたQFEのせいで、アプリケーションが異常な動作を起こすようになったというようなケースです。
デフォルト バージョン ポリシーはXML構成ファイルを使って変更することができます。バージョンニングに関しては、アプリケーション固有のファイルと、マシン全体のファイル (管理者ファイルとも呼ばれます) の 2 つがあります。アプリケーション固有のファイルはアプリケーションと同じディレクトリに置かれています。このファイルに追加されたポリシー ステートメントは、そのアプリケーションにのみ影響を与えます。マシン全体のポリシー ファイルは、現時点ではWindowsディレクトリに置かれています。このファイルは、管理者がマシン上のすべてのアプリケーションに影響を与えるポリシー ステートメントを追加するために使用されます。たとえば、あるアセンブリの特定のバージョンが何らかのセキュリティ ホールを作り出すことが発見され、このアセンブリが決して使われないようにしたい場合などです。
以下に、カスタム ポリシーの例を示します。
特定のバージョンにバインドする
マニフェストに記録されているバージョンではなく、まったく異なるバージョンのアセンブリにバインドしたい場合があります。このシナリオは、XML 構成ファイルの<BindingPolicy>タグを通してサポートされています。このポリシーは、依存ファイルの特定のバージョンへの参照か、依存ファイルのすべてのバージョンへの参照をマッピングするために使用できます。
次の例は、Calcrという名前のアセンブリのすべてのバージョンを、バージョン6.0.0.0にマッピングしています。
<BindingPolicy>
<BindingRedir Name="Calcr"
Originator="32ab4ba45e0a69a1"
Version="*" VersionNew="6.0.0.0"
UseLatestBuildRevision="yes"/>
</BindingPolicy>
自動QFEポリシーをオフにする
このポリシー ステートメントでは、特定のアセンブリへの参照の「自動QFEポリシー」を無効にすることができます。次の例に示すように、<BindingPolicy>タグを使用し、UseLatestBuildRevision属性をNoに設定します。
<BindingPolicy>
<BindingRedir Name="Calcr"
Originator="32ab4ba45e0a69a1"
Version="*" VersionNew="6.1.1212.14"
UseLatestBuildRevision="no"/>
</BindingPolicy>
Safe Mode
Safe Mode (または「ビルド時と同じように実行」) ポリシーは、ビルド時の構成に戻すために使用されます。このポリシーを有効にすると、Common Language Runtimeはマニフェストに記録されている依存ファイルのバージョンをそのままロードします。これは、アプリケーションをビルドし、テストし、最初に導入した時点では正常に動作していた場合に使用されます。Safe Modeは、そのときの状態に戻すために使用される安全ネットのようなものです。次のXMLコードは、特定のアプリケーションのSafe Modeをオンにします。
<BindingMode>
<AppBindingMode Mode="safe"/>
</BindingMode>
「Safe Mode」と「自動QFEポリシーをオフにする」は、どちらも、特定の依存ファイルがバージョン規則に従っていなかったり、誤ってバグを導入してしまった場合に、アプリケーションを正常に動作する状態に戻すために使用されます。
ポリシー解決の各段階
この記事では、アプリケーション プライベートなアセンブリ、共有アセンブリ、グローバル アセンブリ ストア、およびバージョン ポリシーを指定するためのXMLファイルを含めて、バージョンニングと導入に関するいくつかの概念を紹介しました。このセクションでは、Common Language Runtimeがアセンブリを発見し、バージョン ポリシーを適用するときの各段階を説明することで、これらの概念の関連を明らかにします。
アセンブリをロードするプロセスは、Common Language Runtimeが、メタデータに格納された別のアセンブリへの参照を発見したときに開始されます。この参照をもとに、以下のステップを使って、アセンブリのどのバージョンをロードするかが決定されます。
- アプリケーション固有の構成ファイルを調べて、何らかのポリシーが指定されているかどうかを確認します。指定されている場合には、元の参照を、ポリシーの情報を使って変更します。たとえば、参照がバージョン1.0.0.0を指定しており、アプリケーション固有のポリシー ファイルがバージョン2.0.0.0を指定している場合、Common Language Runtimeはもともとバージョン2.0.0.0が要求されたかのように処理を続けます。
- アプリケーション ディレクトリ (およびサブディレクトリ) の中で、プロービングによって、マッチするアセンブリを探します。「マッチする」とは、メジャー番号とマイナー番号が正確に一致することをいいます (QFEポリシーがオフになっていない場合)。
- プロービングによってマッチするアセンブリが見つかったかどうかにかかわらず、グローバル アセンブリ ストアの中でQFEを探します。これにより、管理者は、すべてのアプリケーションに使用させたいバグ フィックスを導入することができます。
- 最後に、管理者ポリシー ファイルを調べます。このファイルを最後に調べるのは、どのバージョンがロードされるかという最終的な決定権が管理者にあるからです。
導入
導入には、少なくとも2つの異なる側面があります。コードのパッケージングと、アプリケーションが実行される各種のクライアントとサーバーへのパッケージの配布です。.NET Frameworkの主な目標は、影響ゼロのインストールとxcopy導入を可能にすることによって、導入 (特に配布の部分) を単純化することです。アセンブリの自己記述的な性質によって、レジストリに対する依存性をなくし、インストール、アンインストール、および複製を大幅に単純化することができます。ただし、xcopyが配布メカニズムとして十分ではないようなシナリオもあります。このようなケースのために、.NET Frameworkは高度なコード ダウンロード サービスと、Windows Installerとの統合を提供しています。
パッケージング
.NET Frameworkの最初のリリースには、3つのパッケージング オプションが用意されています。
-
ビルド時と同じ (DLLとEXE) - 多くのシナリオでは、特殊なパッケージングは必要ありません。アプリケーションは、開発ツールが生成した形式、すなわちDLLとexeの集まりとして導入することができます。
-
Cabファイル - Cabファイルを使うと、アプリケーションを圧縮し、ダウンロードの効率を高めることができます。
-
Windows Installerパッケージ - Microsoft Visual Studio.NETやその他のインストール ツールでは、Windows Installerパッケージ (.msiファイル) を作成することができます。Windows Installerを使うと、アプリケーション修復、オンデマンド インストール、およびその他のMicrosoft Windows 2000のアプリケーション管理機能を使用することができます。
配布シナリオ
.NETアプリケーションは、xcopy、コード ダウンロード、およびWindows Installerを含むさまざまな方法で配布することができます。
WebアプリケーションとXML Web サービスを含む多くのアプリケーションでは、導入は、ファイルのセットをディスクにコピーし、実行するという単純な作業で終了します。アンインストールと複製は、ファイルを削除またはコピーするだけでできてしまいます。
.NET Frameworkは、Webブラウザを使った高度なコード ダウンロード サポートを提供します。この分野では、以下のものを含めて、いくつかの改善が加えられています。
-
影響ゼロ - マシン上にレジストリ項目は作成されません。
-
インクリメンタル ダウンロード - アセンブリの個々の部分は、参照されたときにのみダウンロードされます。
-
アプリケーションに対して隔離されたダウンロード - 1 つのアプリケーションのためにダウンロードされたコードは、マシン上の他のアプリケーションには影響を与えません。コード ダウンロード サポートの主な目標の 1 つは、ユーザーが特定のWebサイトをブラウズした副作用として、何らかの共有コンポーネントの新しいバージョンがダウンロードされ、その新しいバージョンが他のアプリケーションに悪影響を与えるというようなシナリオを防ぐことにあります。
-
Authenticodeダイアログはなし - コード アクセス セキュリティ システムによって、部分的な信頼レベルによってモバイル コードを実行することが可能となります。ユーザーに対して、コードを信頼するかどうかを尋ねるダイアログ ボックスが表示されることはありません。
最後に、.NETはWindows Installerと、Windows 2000のアプリケーション管理機能と完全に統合されています。
要約
.NET Frameworkは、影響ゼロのインストールを可能にし、DLL Hellに対処します。アセンブリは、これらの機能を実現するための、自己記述的でバージョンニング可能な導入単位です。
隔離されたアプリケーションを作成する能力が重要なのは、これによって、他のアプリケーションがシステムに加える変更の影響を受けないアプリケーションを作成できるからです。.NET Frameworkは、アプリケーションのディレクトリ構造の中に導入されるアプリケーション プライベート アセンブリを通して、この種のアプリケーションの作成を支援しています。
サイド バイ サイドは、.NETの共有とバージョンニングのコアにあります。サイド バイ サイドにより、アセンブリの複数のバージョンを同じマシンにインストールし、同時に実行することができます。また、各アプリケーションはそのアセンブリの特定のバージョンを要求することができます。
Common Language Runtimeは、アプリケーションの各部分のバージョン情報の記録を実行時に使用して、依存ファイルの正しいバージョンがロードされるようにしています。アプリケーション開発者と管理者は、バージョン ポリシーを使用して、特定のアセンブリのどのバージョンがロードされるのかをある程度柔軟に選択することができます。
.NET Frameworkは、Windows Installer、コード ダウンロード、および単純なxcopyを含めて、いくつかのパッケージングと配布のオプションを用意しています。