第3章 共有アセンブリ

第2章では、アセンブリをビルド、パッケージング、配布する手順を説明しました。第2章では「プライベートな配布」とでもいうような、アセンブリをそのアプリケーションからだけ利用できるようにアプリケーションのベースディレクトリ(またはそのサブディレクトリ)に配布する場合に焦点を絞っていました。アセンブリをアプリケーションプライベートとして配布すれば、ベンダは名前付け、バージョン管理、アセンブリの動作について、幅広い範囲で制御できます。

本章では、複数のアプリケーションからアクセスできるアセンブリの作成に焦点を当てて解説します。Microsoft .NET Frameworkと共に出荷されているアセンブリは、グローバルにアクセスできるよう配布されているアセンブリの好例です。この理由は、ほぼすべてのマネージアプリケーションが、.NET Frameworkクラスライブラリ(FCL)に入っているMicrosoftが定義した型にアクセスするからです。

第2章で触れたとおり、Windowsには不安定という評判が付きまとっています。不安定さの主要な原因は、アプリケーションのビルドとテストが、他者によって実装されたコードを使って行われているからです。結局のところ、Windows用のアプリケーションを書いていれば、アプリケーションは必ずMicrosoftの開発者が書いたコードを呼び出すわけです。また、開発者が自分のアプリケーションに組み込んで使うことができるコントロールを、多くの企業が開発しています。実は、.NET Frameworkはそれをさらに推し進めており、より多くのコントロールベンダがこれから先、生まれようとしています。

Microsoftの開発者やコントロールの開発者は、リリース後もコードを見直し、バグ修正や機能追加を行います。その後、新しいコードはユーザーのハードディスクにインストールされます。この時点で、以前にインストールされ、これまで問題なく動作していたユーザーのアプリケーションは、それが開発されテストされたときと同じコードを使うことがもうできなくなります。結果として、アプリケーションの動作は予測不可能となり、Windowsの不安定さをさらに増すようになります。

ファイルのバージョン管理は厄介な問題です。もっとはっきり言えば、もしもあるファイルを開いて、ある1ビットを1から0、あるいは0から1に変えただけで、元のファイルを利用していたコードが新しいバージョンでも同じように動作することを保証することは絶対にできないと断言できます。この文章が正しい理由の1つは、多くのアプリケーションがバグを活用してしまっていることです。新しいバージョンでバグが修正されると、アプリケーションは期待どおりに動かなくなってしまいます。

ここに問題があります。アプリケーションを破壊しないことを保証しつつ、同時にバグを修正して機能を追加する方法はあるのでしょうか? 熟慮の結果、筆者は1つの結論に達しました。それは不可能であるということです。しかし、それではだめなことは明白です。ファイルはバグと共に出荷されます。さらに開発者は新しい機能を追加したがります。アプリケーションがこれまで同様に動作し続けるという期待と共に、新しいファイルを出荷する方法が何かあるはずです。そして正しく動作しなかった場合は、アプリケーションを「最後に正しく動いたときの状態」に戻すことができる何か手軽な方法がなければなりません。

本章では、.NET Frameworkがバージョン管理のために最初から持っている機能について解説します。しかし、ここから先の説明は非常に複雑です。ここでは、共通言語ランタイム(CLR)に組み込まれている多くのアルゴリズムやルール、ポリシーについて説明します。アプリケーション開発者が使わなければならない、多くのツールやユーティリティについても触れます。これらが複雑なのは、最初に触れたとおり、バージョン管理の問題そのものがとても厄介だからです。

3.1 | 2種類のアセンブリと2種類の配布

.NET Frameworkは2種類のアセンブリをサポートします。これは、「あいまいな名前のアセンブリ」と「厳密名付きのアセンブリ」です。

重要 ちなみに、「あいまいな名前のアセンブリ」という用語は.NET Frameworkのドキュメントには出てきません。これは、筆者の造語です。というのも、ドキュメントには厳密名を持たないアセンブリを示す用語がありません。そこでどのアセンブリについて言っているのか紛らわしくないように、言葉を作成することにしました。

あいまいな名前のアセンブリと厳密名付きのアセンブリの構造は、まったく同じです。つまり、同じPEファイル形式を使い、PEヘッダー、CLRヘッダー、メタデータ、マニフェストテーブルなどの第2章で既に扱った内容は、まったく同じです。どちらのアセンブリも、同じツール(たとえばC#コンパイラやAL.exe)を使って作成することができます。2つのアセンブリの本当の違いは、厳密名付きのアセンブリは公開元を一意に識別できる公開キー/秘密キーのペアで署名されている点です。このキーペアによってアセンブリが一意に識別でき、セキュリティが確保され、バージョン管理が可能になります。また、ユーザーのハードディスク上からインターネット上まで、あらゆる場所に配布できるようになります。アセンブリを一意に識別できるようにすることで、アプリケーションが厳密名付きのアセンブリをバインドするときに、CLRが特定の「安全であることがわかっている」ポリシーを強制できるようにしています。本章では、厳密名付きのアセンブリとは何か、CLRが適用するのはどのようなポリシーなのかに重点を置いて説明します。

アセンブリは2つの方法で配布できます。プライベートとグローバルです。プライベートに配布されたアセンブリとは、アプリケーションのベースディレクトリかそのサブディレクトリに配布されているアセンブリです。あいまいな名前のアセンブリは、プライベートにしか配布できません。プライベートに配布されるアセンブリについては、第2章で既に説明しました。一方、グローバルに配布されたアセンブリとは、CLRが最初から知っていて、アセンブリを検索するときに利用する特定の場所に配布されたアセンブリです。厳密名付きのアセンブリはプライベートにもグローバルにも配布できます。本章では、厳密名付きのアセンブリの作成と配布の方法を解説します。表3-1にアセンブリの種類と配布方法をまとめます。

▼表3-1 アセンブリの配布方法

アセンブリの種類 プライベート配布 グローバル配布  
あいまいな名前のアセンブリ 不可  
厳密名付きアセンブリ  

3.2 | アセンブリに厳密名を付ける

アセンブリが複数のアプリケーションからアクセスされる場合、アセンブリは既知のディレクトリに置かれていなければなりません。また、アセンブリへの参照が検出されたときに、CLRがその場所を知っていなければなりません。しかし、これには問題があります。2つ(またはそれ以上)の会社が、アセンブリを同じファイル名で作成してしまう可能性があるということです。そのような事態が起こると、同じ既知のディレクトリに両方のファイルがコピーされることになり、後でインストールされた方が「勝って」しまうので、上書きされてしまった方のアセンブリを利用していたアプリケーションはすべて期待どおりの動作をしなくなってしまいます(これはまさに今日のWindowsにDLL地獄が存在する理由そのものです)。

このように、アセンブリを単純にファイル名を使って識別するのでは、十分ではありません。CLRはアセンブリを一意に識別する別のメカニズムを必要としています。これが厳密名付きアセンブリの存在意義です。厳密名付きアセンブリはアセンブリを一意に識別する4つの属性を持っています。ファイル名(拡張子なし)、バージョン番号、カルチャ、公開キートークン(公開キーから生成された値)の4つです。次の文字列は、4つのまったく異なるアセンブリファイルを識別しています。

"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=1.0.8123.0, Culture="en-US", PublicKeyToken=b77a5c561934e089" "MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

最初の文字列はMyTypes.dllというアセンブリファイルを表します。この企業では、このアセンブリのバージョン1.0.8123.0を作成しています。このアセンブリは、Cultureneutralに設定されていることからわかるように、特にカルチャを意識しません。当然どのような企業でも、バージョン番号が1.0.8123.0でカルチャがneutralのアセンブリを開発することができます。

この企業のアセンブリを、ほかの企業のアセンブリが偶然同じ属性を持っている場合でも区別できるようにする方法が必要です。いくつかの理由で、MicrosoftはGUIDやURL、URNなどではなく、標準的な公開キー暗号技術を使うことにしました。特に、暗号化技術を使えば、ハードディスクにインストールされたアセンブリのビットの整合性チェックを行うことができます。公開元ごとにセキュリティ設定を行うこともできます。これらのテクニックについては本章で後述します。

そこで、アセンブリに一意のマークを付けるために、公開キー/秘密キーのペアを入手する必要があります。そして、公開キーをアセンブリに関連付けます。2つの企業が同じ公開キー/秘密キーのペアを持つことはないので、2つの企業が同じ名前、バージョン、カルチャのアセンブリを作成したとしても、それが競合することがなくなります。

注 アセンブリの名前を構築したり、名前のさまざまな部分を取り出すのを支援するために、System.Reflection.AssemblyNameクラスというヘルパーが用意されています。このクラスには、CultureInfoFullNameKeyPairNameVersionなどのpublicなインスタンスプロパティがあります。また、GetPublicKeyGetPublicKeyTokenSetPublicKeySetPublicKeyTokenなどのpublicなインスタンスメソッドもあります。

第2章では、アセンブリファイルの命名法と、アセンブリにバージョン番号とカルチャを設定する方法を解説しました。あいまいな名前のアセンブリでも、マニフェストメタデータにバージョン番号とカルチャを持つことはできます。しかし、CLRはバージョン番号を常に無視して、サテライトアセンブリを検索してプローブを行うときに、カルチャ情報だけを利用します。あいまいな名前のアセンブリはプライベートにしか配布できないので、CLRは単純なアセンブリの名前(.dllか.exe拡張子を付けたもの)を利用して、アプリケーションのベースディレクトリや、XML構成ファイルのprobing要素のprivatePath属性に記述してあるサブディレクトリの中で、アセンブリを検索します。

厳密名付きアセンブリも、ファイル名、アセンブリバージョン、カルチャを持っています。さらに、厳密名付きアセンブリは公開元の秘密キーで署名されています。

厳密名付きアセンブリを作成する最初の手順は、.NET Framework SDKとVisual Studio .NETに付属する厳密名ツール(SN.exe)を使ってキーを取得することです。このツールは、コマンドラインスイッチを使い分けることで無数の機能を提供しています。SN.exeのコマンドラインスイッチはすべて大文字と小文字を区別することに注意してください。公開キー/秘密キーのペアを生成するには、SN.exeを次のように実行します。

SN -k MyCompany.snk

このコマンドで、SN.exeはMyCompany.snkというファイルを作成します。このファイルに、公開キーと秘密キーを表す数値がバイナリ形式で詰まっています。

公開キーはとても大きな桁数の数値です。次のコマンドを実行すれば、公開キーを見ることができます。

SN -tp MyCompany.snk

これを実行すると、次のような出力が得られます。

Microsoft(R) .NET Framework Strong Name Utility Version 1.0.3705.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. 公開キー 0702000000240000525341320004000001000100c3185e07a659106728e0f4b12a94ed8e4a8ff4 13c92479c9b074d0bafca70b872727405a10accc67db6110470381c1c81bbf305c5cae86185cdd 5e4855282cb29320663402387e25368c21979c23eaab95f5e1e1c39a73ea713406ea6cc2f8e8e3 add159c85f3b21badbfc2672f52564b27c01317f1dcc701c52274239bb7dc96b55c1662e541c08 aaac61ec58b5a9f8ed6d754bde4ba467488d15482f6e03390bd4403fe09d50da6c3e270ec2007c 15a242a635f32298d793c8684a885a59e30948a476ac8696a4f4382e4fffb4116158acfe519ce0 4d35c1a97c3cc91d5d0e64f9040ab17a40b796ae6004a72b2deb26416c91561a93917b31aceb01 29e2e201d1002d07e75e96575b553a7116ffd35b96cd3bd4cdd62245baa52a30fdfecf832c1f06 702f70e6bfaa3cc801460f20d41e07fe2f62e27c908110b47946fb13916814fe24e339270c5da6 30b481511411d63edf44a3db7404c80ad4b4e91e1988bc882e580d769a02264a50a4ee688b6eb5 fa2a5b671e27d3a26855daa1cf0908bbbfd5290785b469297ab4cb16de0325fa3d766c61c6704d b4c1c9dacc2b3c8968fdfbcfbb5f8d7836240e228e6bd92357dd2b8b753a9653279783e48e1d1a 21dcb93934187eba155e83f2b217aee7423b9a69cd19f147306330b8b78119be460291621b4c78 9af7a18cee433e287f5d6d9df26a88ada72eb6a0f9785ecef147d0591be79cc5dd85c3f29eb4e6 a2ea564ec3b4e13e15d561f4d1475e91094c64bbff751bfeccf6787929b71ac446e1dda60d9224 fe43880dc3b8efeb75bab9 公開キー トークン db9908f2b9a0084c

公開キーはサイズが大きすぎるため、そのまま扱うのには適していません。そこで、開発者(とエンドユーザー)にとって使いやすいように、「公開キートークン」が作成されました。公開キートークンは、公開キーの64ビットのハッシュ値です。SN.exeの-tpスイッチを使うと、完全な公開キーを出力した後に、対応する公開キートークンが出力されます。

さて、公開キーと秘密キーペアの作成方法が理解できたので、後は厳密名付きアセンブリを作成するだけです。これは簡単で、ソースコードにSystem.Reflection. AssemblyKeyFileAttribute属性のインスタンスを適用すればいいのです。

[assembly:AssemblyKeyFile("MyCompany.snk")]

ソースコード中でこの属性を見つけると、コンパイラは指定されたファイル(MyCompany.snk)を開いて、その秘密キーでアセンブリに署名をし、公開キーをマニフェストに埋め込みます。マニフェストを持っているアセンブリファイルだけしか署名されないことに注意してください。アセンブリのほかのファイルは、明示的に署名できません。

厳密名付きアセンブリをビルドすると、アセンブリのFileDefマニフェストメタデータテーブルにアセンブリを構成するファイルの一覧が作成されます。個々のファイル名がマニフェストに追加されるときに、ファイルの中身をハッシュした値が同時にFileDefテーブルに書き込まれます。このときのハッシュアルゴリズムは、AL.exeの**/algidスイッチか、アセンブリレベルのカスタム属性としてSystem.Reflection. AssemblyAlgorithmIdAttribute**を指定すれば、変更することができます。デフォルトではSHA-1アルゴリズムが使われています。ほとんどのアプリケーションではこれで十分です。

マニフェストを持っているPEファイルがビルドされると、図3-1のとおりPEファイルの中身全部がハッシュされます。ここで使われるハッシュアルゴリズムは常にSHA-1で、変更できません。このハッシュ値は通常100~200バイトくらいのサイズになります。このハッシュ値が公開元の秘密キーで署名され、その結果のRSAデジタル署名がPEファイルの予約セクション(ハッシュには含まれない)に書き込まれます。PEファイルのCLRヘッダーも、ファイルの中でデジタル署名が埋め込まれている場所を指し示すように更新されます。

図3-1 アセンブリに署名する

▲図3-1 アセンブリに署名する

公開元の公開キーは、PEファイルのAssemblyDefマニフェストメタデータテーブルにも埋め込まれます。ファイル名、アセンブリのバージョン、カルチャ、公開キーの組み合わせが、アセンブリの厳密名になります。これは一意であることが保証されています。2つの企業が同じ公開キーを持っている「Calculus」アセンブリを作成することはできません(同じキーペアを共有していないというのが前提ですが)。

ここまできたら、アセンブリとそのすべてのファイルをパッケージングして出荷する準備ができたことになります。

第2章で解説したとおり、ソースコードをコンパイルすると、そのコードが参照している型とメンバをコンパイラが検出します。参照しているアセンブリはコンパイラに対して指定しなければなりません。C#コンパイラの場合は、/referenceコマンドラインスイッチを使って指定します。コンパイラの仕事の1つは、AssemblyRefメタデータテーブルを、出力されるマネージモジュールに埋め込むことです。AssemblyRefメタデータテーブルの各エントリには、参照先のアセンブリの名前(パスや拡張子は除く)、バージョン番号、カルチャ、そして公開キーの情報が含まれています。

重要 公開キーはとても大きな数値であり、1つのアセンブリはほかの多くのアセンブリを参照することがあるため、このままでは出力されるファイルのほとんどを公開キー情報が占めてしまうことになりかねません。スペースの節約のために、Microsoftは公開キーをハッシュして、そのハッシュ値の最後の8バイトを取り出すことにしました。この縮小された値は、統計的に一意であるため、システム上で利用しても安全であることがわかっています。この切り詰めた値は公開キートークンと呼ばれ、実際にAssemblyRefテーブルに格納されます。通常、開発者とエンドユーザーは、この公開キートークンを見る機会の方が、完全な公開キーを見ることよりも多いでしょう。

第2章で使用したJeffTypes.dllファイルのAssemblyRefメタデータ情報は、次のようになっています。

AssemblyRef #1 ------------------------------------------------------- Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: 0x00000ce4 Revision Number: 0x00000000 Locale: HashValue Blob: ea f7 8f 4c 9d 50 3e 55 b7 21 0a 26 34 d3 88 8c c6 29 dc cd Flags: [none] (00000000)

ここから、JeffTypes.dllは次の属性にマッチするアセンブリに含まれている型を参照していることがわかります。

"MSCorLib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

残念なことに、ILDasm.exeはCultureと表記すべきところをLocaleと表記しています。Microsoftは将来のバージョンでこの点を修正するでしょう。

JeffTypes.dllのAssemblyDefメタデータテーブルを見てみると、次のようになっています。

Assembly ------------------------------------------------------- Token: 0x20000001 Name : JeffTypes Public Key : Hash Algorithm : 0x00008004 Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: 0x00000253 Revision Number: 0x00005361 Locale: Flags : [SideBySideCompatible] (00000000)

これは、次の文字列と等価です。

"JeffTypes, Version=1.0.595.21345, Culture=neutral, PublicKeyToken=null"

この文字列では公開キートークンが指定されていません。第2章で使ったJeffTypes.dllアセンブリは秘密キーで署名されていないので、あいまいな名前のアセンブリになっています。

SN.exeを使ってキーファイルを生成し、AssemblyKeyFileAttribute属性をソースコードに追加して再コンパイルすれば、アセンブリは署名されます。AL.exeでアセンブリをビルドする場合は、/keyfileスイッチをAssemblyKeyFileAttribute属性の代わりに使います。できあがった新しいアセンブリのメタデータをILDasm.exeでのぞいてみれば、AssemblyDefエントリのPublicKeyフィールドにバイト列が表示されて、アセンブリが厳密名を持っていることがわかるでしょう。ちなみに、AssemblyDefエントリには公開キートークンではなく、常に完全な公開キーが保存されます。ファイルが改ざんされていないことを確認するために、完全な公開キーが必要なのです。厳密名付きアセンブリによる改ざんの防止については、本章で後述します。

3.3 | グローバルアセンブリキャッシュ

厳密名付きアセンブリの作成方法が理解できたので、次はこのアセンブリの配布方法と、CLRがそれを見つけてロードする方法を解説しましょう。

複数のアプリケーションからアクセスされるアセンブリは、既知のディレクトリに置かれていなければならないほか、アセンブリへの参照が検出されたときに、CLRが自動的にその場所を探すようになっていなければなりません。この既知のディレクトリのことを、グローバルアセンブリキャッシュ(GAC)と呼びます。GACは通常は次の場所にあります。

C:\Windows\Assembly\GAC

GACディレクトリは多くのサブディレクトリを含む構造になっていて、そのサブディレクトリの名前はある特定のアルゴリズムで決定されています。アセンブリファイルは手動でコピーしてはいけません。GACにコピーする作業はツールを使って行います。GACの内部構造や、正しいサブディレクトリの名前を生成する方法はツールに埋め込まれています。

開発中やテスト中にGACに厳密名付きアセンブリをインストールするために、最も一般的に使われるツールがGACUtil.exeです。このツールをコマンドライン引数なしで実行すると、次のような使用方法が表示されます。

Microsoft (R) .NET Global Assembly Cache Utility.  Version 1.0.3705.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Usage: Gacutil <option> [<parameters>]  Options:     /i     Installs an assembly to the global assembly cache.  Include the     name of the file containing the manifest as a parameter.     Example:  /i myDll.dll        /if     Installs an assembly to the global assembly cache and forces     overwrite if assembly already exists in cache.  Include the     name of the file containing the manifest as a parameter.     Example:  /if myDll.dll        /ir     Installs an assembly to the global assembly cache with traced     reference. Include the name of file containing manifest,     reference scheme, ID and description as parameters     Example:  /ir myDll.dll FILEPATH c:\apps\myapp.exe MyApp        /u[ngen]     Uninstalls an assembly. Include the name of the assembly to     remove as a parameter. If ngen is specified, the assembly is     removed from the cache of ngen'd files, otherwise the assembly     is removed from the global assembly cache     Examples:       /ungen myDll       /u myDll,Version=1.1.0.0,Culture=en,PublicKeyToken=874e23ab874e23ab          /ur     Uninstalls an assembly reference. Include the name of the     assembly, type of reference, ID and data as parameters.     Example:         /ur myDll,Version=1.1.0.0,Culture=en,PublicKeyToken=874e23ab874e23ab                        FILEPATH c:\apps\myapp.exe MyApp                           /uf     Forces uninstall of an assembly by removing all install references     Include the full name of the assembly to remove as a parameter..     Assembly will be removed unless referenced by Windows Installer.     Example:         /uf myDll,Version=1.1.0.0,Culture=en,PublicKeyToken=874e23ab874e23ab          /l     Lists the contents of the global assembly cache. Allows optional     assembly name parameter to list matching assemblies only        /lr     Lists the contents of the global assembly cache with traced     reference information. Allows optional assembly name parameter     to list matching assemblies only        /cdl     Deletes the contents of the download cache        /ldl     Lists the contents of the downloaded files cache        /nologo     Suppresses display of the logo banner        /silent     Suppresses display of all output        /?     Displays this help screen

GACUtil.exeに**/iスイッチを指定すると、アセンブリをGACにインストールできます。また、/u**スイッチを指定すると、GACからアセンブリをアンインストールすることができます。あいまいな名前のアセンブリをGACに入れることはできないので注意してください。GACUtil.exeにあいまいな名前のアセンブリのファイル名を渡すと、次のエラーメッセージが表示されます。

Failure adding assembly to the cache: Attempt to install an assembly without a strong name.

注 デフォルトでは、GACはWindowsのAdministratorsグループのメンバでなければ操作できません。このグループのメンバでないユーザーがGACUtil.exeを使ってアセンブリのインストールやアンインストールを試みても、失敗します。

GACUtil.exeの**/iスイッチは、開発者のテスト用としては非常に便利です。しかし、GACUtil.exeを使って実行環境にアセンブリを配布しようとする場合は、GACUtil.exeの/irスイッチを使うことをお勧めします。アンインストールのときは/urスイッチを利用します。/ir**スイッチは、インストールされるアセンブリをWindowsのインストール/アンインストールエンジンに統合します。基本的にこのスイッチは、どのアプリケーションがアセンブリを必要としているのかをシステムに対して通知し、アプリケーションとアセンブリを結び付ける機能を提供します。

注 厳密名付きアセンブリがキャビネット(.cab)ファイルにパッケージングされていたり、なんらかの形で圧縮されている場合、アセンブリのファイルをまずテンポラリの領域で解凍しなければなりません。次に、その解凍されたファイルを使ってGACUtil.exeでアセンブリファイルをGACにインストールします。一度GACにインストールすれば、テンポラリファイルは削除してかまいません。

GACUtil.exeツールは、エンドユーザー用の.NET Framework再頒布パッケージには含まれていません。アプリケーションがGACに配布すべきアセンブリを持っている場合は、Microsoft Windows Installer(MSI)Version 2以降を使う必要があります。エンドユーザーのコンピュータに必ず存在し、しかもアセンブリをGACにインストールする機能も併せて提供しているのはWindows Installerだけです(MSIExec.exeを実行すれば、どのバージョンのWindows Installerがインストールされているかがわかります)。

注 アセンブリをGACに入れてグローバルに配布するということは、アセンブリを登録するということです(登録といっても、Windowsのレジストリにはまったく何の影響も与えません)。アセンブリをGACにインストールすると、アプリケーションのインストール、バックアップ、復元、移動の単純化という目標をないがしろにしてしまいます。これらの「単純化」は、実際にはプライベートアセンブリだけを利用して、アセンブリをグローバルに配布することを避けるようにしなければ、実現されません。

GACにアセンブリを「登録」する目的は何でしょうか? たとえば、2つの企業がCalculusという名前の、Calculus.dllというファイル1つだけで構成されるアセンブリを開発したとしましょう。だれが見てもわかるとおり、これら2つのファイルは同じディレクトリには置けません。2番目にインストールしたファイルが、最初のファイルを上書きしてしまうので、いくつかのアプリケーションを破壊してしまいます。ツールを使ってアセンブリをGACにインストールすると、C:¥Windows¥Assembly¥GACの下にサブディレクトリが作成されて、アセンブリファイルはそこにコピーされます。

一般的には、GACのサブディレクトリをだれかが検証する必要はないので、GACの構造を知っていてもあまり意味はありません。ツールとCLRが構造を理解していればいいのです。ただ読者の好奇心を満たすために、次の節でGACの内部構造を解説しておきます。

.NET Frameworkをインストールすると、エクスプローラのシェル拡張(ShFusion.dll)がインストールされます。このシェル拡張もGACの内部構造を知っていて、GACの内容を見やすい、ユーザーフレンドリな形式で表示してくれます。エクスプローラでC:¥Windows¥Assemblyディレクトリに移動すると、図3-2の画面が表示されます。この画面には、GACにインストールされているアセンブリが表示されます。行ごとに、アセンブリの名前、種類、バージョン番号、カルチャ(設定されていれば)、公開キートークンが表示されます。

図3-2 エクスプローラのシェル拡張を使ってGACにインストールされているアセンブリを見る<

▲図3-2 エクスプローラのシェル拡張を使ってGACにインストールされているアセンブリを見る

エントリを選択して、マウスの右ボタンをクリックすると、コンテキストメニューが表示されます。コンテキストメニューには[削除]と[プロパティ]が表示されます。[削除]メニューからは、選択されたアセンブリファイルをGACから削除して、GACの内部構造を修正することができます。[プロパティ]メニューを選択すると、図3-3のようなプロパティダイアログボックスが表示されます。更新日時には、アセンブリがGACに追加された日時が表示されています。[バージョン]タブを選択すると、図3-4のダイアログボックスが表示されます。

図3-3 [Systemのプロパティ]ダイアログボックスの[全般]タブ

▲図3-3 [Systemのプロパティ]ダイアログボックスの[全般]タブ

図3-4 [Systemのプロパティ]ダイアログボックスの[バージョン]タブ

▲図3-4 [Systemのプロパティ]ダイアログボックスの[バージョン]タブ

マニフェストを持っているアセンブリファイルを、エクスプローラのウィンドウにドラッグアンドドロップすることもできます。この操作を行うと、シェル拡張はアセンブリのファイルをGACにインストールします。テスト用であれば、GACUtil.exeツールを使うよりも簡単だと思う開発者もいることでしょう。

3.3.1 | GACの内部構造

簡単に言うと、GACは厳密名付きアセンブリとサブディレクトリとの関係を維持するために存在しています。CLRには、アセンブリの名前、バージョン、カルチャ、公開キートークンをとる内部関数が定義されています。指定したアセンブリが見つかれば、この関数はそれが入っているサブディレクトリへのパスを返します。

コマンドプロンプトでC:¥Windows¥Assembly¥GACディレクトリに移動すれば、GACにインストールされているアセンブリごとに1つずつサブディレクトリがあることがわかるでしょう。筆者のコンピュータのGACディレクトリは次のようになっています(一部省略しています)。

ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は 2823-D5A8 です c:\WINDOWS\assembly\GAC のディレクトリ 2002/03/20 12:33 <DIR> . 2002/03/20 12:33 <DIR> .. 2002/03/19 14:48 <DIR> Accessibility 2002/03/19 16:41 <DIR> ADODB 2002/03/19 16:41 <DIR> CRVsPackageLib 2002/03/19 14:48 <DIR> Microsoft.JScript 2002/03/19 16:41 <DIR> Microsoft.mshtml 2002/03/19 16:41 <DIR> Microsoft.StdFormat 2002/03/19 14:48 <DIR> Microsoft.VisualBasic 2002/03/19 16:41 <DIR> Microsoft.VisualBasic.Compatibility 2002/03/19 16:41 <DIR> Microsoft.VisualBasic.Compatibility.Data 2002/03/19 14:48 <DIR> Microsoft.VisualBasic.Vsa 2002/03/19 14:48 <DIR> Microsoft.VisualC 2002/03/19 16:41 <DIR> Microsoft.VisualStudio.VCCodeModel 2002/03/19 16:41 <DIR> Microsoft.VisualStudio.VCProject 2002/03/19 16:41 <DIR> Microsoft.VisualStudio.VCProjectEngine 2002/03/19 16:41 <DIR> Microsoft.VisualStudio.VSHelp 2002/03/19 16:41 <DIR> Microsoft.VisualStudioAnalyzer.Automation 2002/03/19 16:41 <DIR> Microsoft.VisualStudioAnalyzer.EventFire 2002/03/19 16:41 <DIR> Microsoft.VisualStudioAnalyzer.EventInstall 2002/03/19 16:41 <DIR> Microsoft.VisualStudioAnalyzer.EventParsing 2002/03/19 16:41 <DIR> Microsoft.VisualStudioAnalyzer.EventSubscri ber 2002/03/19 16:41 <DIR> Microsoft.VisualStudioAnalyzer.PrimaryEvent Collector 2002/03/19 14:48 <DIR> Microsoft.Vsa 2002/03/19 14:48 <DIR> Microsoft.Vsa.Vb.CodeDOMProcessor 2002/03/19 14:48 <DIR> Microsoft_VsaVb 2002/03/19 14:48 <DIR> System 2002/03/19 14:48 <DIR> System.Configuration.Install 2002/03/19 14:48 <DIR> System.Data 2002/03/19 14:48 <DIR> System.Design 2002/03/19 14:48 <DIR> System.Design.resources 2002/03/19 14:48 <DIR> System.DirectoryServices 2002/03/19 14:48 <DIR> System.Drawing 2002/03/19 14:48 <DIR> System.Drawing.Design 2002/03/19 14:48 <DIR> System.EnterpriseServices 2002/03/19 14:48 <DIR> System.Management 2002/03/19 14:48 <DIR> System.Messaging 2002/03/19 14:48 <DIR> System.resources 2002/03/19 14:48 <DIR> System.Runtime.Remoting 2002/03/19 14:48 <DIR> System.Runtime.Serialization.Formatters.Soap 2002/03/19 14:48 <DIR> System.Security 2002/03/19 14:48 <DIR> System.ServiceProcess 2002/03/19 14:48 <DIR> System.Web 2002/03/19 14:48 <DIR> System.Web.RegularExpressions 2002/03/19 14:48 <DIR> System.Web.Services 2002/03/19 14:48 <DIR> System.Windows.Forms 2002/03/19 14:48 <DIR> System.Xml                0 個のファイル                   0 バイト              107 個のディレクトリ   4,425,101,312 バイトの空き領域

この中のあるディレクトリに移動すると、さらに次のようなサブディレクトリがあるのがわかります。筆者のSystemディレクトリの内容は次のとおりです。

 ドライブ C のボリューム ラベルがありません。  ボリューム シリアル番号は 2823-D5A8 です    c:\WINDOWS\assembly\GAC\System のディレクトリ   2002/03/19 14:48 <DIR> . 2002/03/19 14:48 <DIR> .. 2002/03/19 14:48 <DIR> 1.0.3300.0__b77a5c561934e089                0 個のファイル                   0 バイト                3 個のディレクトリ   4,425,101,312 バイトの空き領域

Systemディレクトリには、コンピュータにインストールされているすべてのSystem.dllアセンブリごとにサブディレクトリが1つずつあります。筆者の場合、System.dllアセンブリの1つのバージョンだけがインストールされています。

"System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

属性はアンダースコアで区切られて、「バージョン_カルチャ_公開キートークン」の形式になっています。この例ではカルチャ情報がないので、neutralになります。このサブディレクトリの中には、Systemアセンブリの厳密名付きバージョンを構成するファイル(System.dllなど)が格納されています。

重要 GACとは、複数バージョンのアセンブリを持たせるための構造であることがわかるでしょう。たとえば、GACはバージョン1.0.0.0と2.0.0.0のCalculus.dllを保持することができます。アプリケーションがバージョン1.0.0.0のCalculus.dllを使って開発されテストされている場合、CLRはたとえ新しいバージョンのアセンブリが存在して、しかもGACに登録されていたとしても、そのアプリケーションにバージョン1.0.0.0のCalculus.dllをロードし続けます。これはCLRがアセンブリをロードするときのデフォルトのポリシーです。このポリシーによって、新しいバージョンのアセンブリが既存のアプリケーションに影響を与えないようにしています。このポリシーは、いくつかの方法で変更できます。それについては本章で後述します。

3.4 | 厳密名付きアセンブリを参照するアセンブリのビルド

アセンブリをビルドするときには、必ずほかの厳密名付きアセンブリを参照します。System.ObjectがMSCorLib.dllで定義されているので、これは常に実行されます。ただし、あるアセンブリがMicrosoft、サードパーティ、または自社が開発した厳密名付きアセンブリの中の型を参照することもよくあります。

第2章では、CSC.exeの**/reference**コマンドラインスイッチを使って、参照したいアセンブリのファイル名を指定する方法を紹介しました。ファイル名がフルパスの場合は、CSC.exeはそのファイルをロードして、そのファイルのメタデータ情報を使ってアセンブリをビルドします。パスなしでファイル名だけを指定した場合は、CSC.exeはアセンブリを次のディレクトリから検索します(検索する順番で記述しています)。

  1. 現在のディレクトリ。
  2. 出力するアセンブリを作成するためにコンパイラ自身が使っているCLRが格納されているディレクトリ。MSCorLib.dllは常にこのディレクトリから取得されます。このディレクトリのパスは次のようなものです。
    C:¥WINDOWS¥Microsoft.NET¥Framework¥v1.0.3705
  3. CSC.exeの/libコマンドラインスイッチで指定されたディレクトリ。
  4. LIB環境変数で指定されたディレクトリ。

つまり、MicrosoftのSystem.Drawing.dllを参照するアセンブリの場合、CSC.exeに**/reference:System.Drawing.dll**スイッチを指定して実行します。コンパイラは上記のディレクトリを順に検索し、コンパイラ自身が使っているCLRが格納されているディレクトリからSystem.Drawing.dllファイルを発見します。コンパイル時にこのディレクトリでアセンブリが発見されるわけですが、これは実行時にロードされるアセンブリが入っているディレクトリではありません。

既におわかりのように、.NET Frameworkをインストールすると、Microsoftのアセンブリファイルは同じものが2つ作成されます。1セットはCLRのディレクトリへ、もう1つのセットはGACにそれぞれインストールされます。CLRディレクトリのファイルは、開発者がアセンブリをビルドしやすいようにするためのものです。GACにあるものが、実行時にロードされるアセンブリです。

CSC.exeが、参照されているアセンブリをGACから検索しない理由は、C:¥WINDOWS¥Assembly¥GAC¥System.Drawing¥1.0.3300.0__b03f5f7f11d50a3a¥System.Drawing.dllのような、長くて意味不明なパスを入力しなければならないからです。また、長いけれども少しは見やすい、"System.Drawing, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" という文字列を指定させられたかもしれません。これら2つの解決策よりも、ユーザーのハードディスクに同じアセンブリファイルを2つ持つことの方がよいと判断した結果、現状のようになったのです。

この節を終える前に、「応答ファイル」について説明しておきましょう。応答ファイルは、コンパイラに対する一連のコマンドラインスイッチが記述されているテキストファイルです。CSC.exeを実行すると、コンパイラは応答ファイルを開き、記述されているスイッチを、あたかもコマンドラインで指定したかのように利用します。コンパイラに対して応答ファイルを使うように指定するには、@記号の後ろにファイル名を付けます。たとえば、次のような内容のMyProject.rspファイルがあるとします。

/out:MyProject.exe /target:winexe

CSC.exeにこの設定を使わせるには、次のコマンドを実行します。

csc.exe @MyProject.rsp CodeFile1.cs CodeFile2.cs

これによって、C#コンパイラは出力ファイルの名前と作成するアセンブリのターゲットを理解します。ご覧のとおり、プロジェクトをコンパイルするたびにコマンドライン引数を入力する必要がなくなるので、応答ファイルはとても便利です。

C#コンパイラは、複数の応答ファイルをサポートします。コマンドラインで明示的に指定したファイル以外に、コンパイラは自動的にCSC.rspという名前のファイルを検索します。CSC.exeは、起動されるとカレントディレクトリでローカルのCSC.rspファイルを検索します。このファイルに、プロジェクト固有の設定を記述しておくことができます。コンパイラは、CSC.exeと同じディレクトリでも、グローバルのCSC.rspファイルを検索します。すべてのプロジェクトで設定したい内容は、このファイルに記述しておくことができます。コンパイラはこれらの応答ファイルの内容をすべて使います。ローカルとグローバルの応答ファイルで競合する設定がある場合は、ローカルファイルの内容がグローバルファイルの内容を上書きします。同様に、明示的にコマンドラインに指定された設定は、ローカルの応答ファイルの設定を上書きします。

.NET Frameworkをインストールすると、デフォルトのグローバルCSC.rspファイルがインストールされます。このファイルには次のスイッチが記述されています。

# This file contains command-line options that the C# # command line compiler (CSC) will process as part # of every compilation, unless the "/noconfig" option # is specified. # Reference the common Framework libraries /r:Accessibility.dll /r:Microsoft.Vsa.dll /r:System.Configuration.Install.dll /r:System.Data.dll /r:System.Design.dll /r:System.DirectoryServices.dll /r:System.dll /r:System.Drawing.Design.dll /r:System.Drawing.dll /r:System.EnterpriseServices.dll /r:System.Management.dll /r:System.Messaging.dll /r:System.Runtime.Remoting.dll /r:System.Runtime.Serialization.Formatters.Soap.dll /r:System.Security.dll /r:System.ServiceProcess.dll /r:System.Web.dll /r:System.Web.RegularExpressions.dll /r:System.Web.Services.dll /r:System.Windows.Forms.Dll /r:System.XML.dll

通常のプロジェクトをビルドする場合、コンパイラは上記のアセンブリへの参照があるものと仮定しています。AssemblyRefエントリは、これらのアセンブリの中の型やメンバに対して、ソースコードから参照しない限り作成されません。この応答ファイルは、開発者にとっては非常に便利です。Microsoftが公開したさまざまなアセンブリで定義されている型と名前空間は、/referenceコマンドラインスイッチを指定しなくても参照できます。もちろん、グローバルのCSC.rspファイルに独自のスイッチを付加するのも、何の問題もありません。そうすればさらに開発が簡単になることでしょう。

注 コマンドラインスイッチで/noconfigを指定すれば、ローカルとグローバルのCSC.rspを両方とも使わないように、コンパイラに指示できます。

3.5 | 厳密名付きアセンブリは改ざんできない

秘密キーを使ってファイルに署名することにより、対応する公開キーの持ち主がそのアセンブリを作成したことを裏付けることができます。アセンブリがGACにインストールされると、システムはマニフェストを持っているファイルの内容をハッシュして、その値をPEファイルに埋め込まれているRSAデジタル署名(を公開キーで署名解除した値)と比較します。もし両者が等しければ、ファイルの内容は改ざんされておらず、公開元の秘密キーと対応する公開キーを持っていることが確認されます。さらに、システムはアセンブリのほかのファイルの内容もハッシュして、その値をマニフェストファイルのFileDefテーブルに書いてある値と比較します。1つでも値が一致しない場合は、アセンブリのファイルの一部が改ざんされたことになるため、GACへのインストールは失敗します。

重要 このメカニズムでは、ファイルの内容が改ざんされていないことが裏付けられるだけです。アセンブリの公開元がだれなのかについては、この公開キーを作成したのが署名に公開元として記述されている企業であり、その秘密キーが絶対に盗まれたりしていないという確信を持っていない限りは、わからないと言わざるを得ません。公開元が自分のアイデンティティをアセンブリに関連付けたい場合は、MicrosoftのAuthenticodeテクノロジを追加で利用しなければなりません。

アプリケーションがアセンブリにバインドする必要がある場合、CLRは参照しているアセンブリのプロパティ(名前、バージョン、カルチャ、公開キー)を使って、GACからアセンブリを検索します。参照しているアセンブリが見つかった場合は、サブディレクトリが返されて、マニフェストを持っているファイルがロードされます。この方法でアセンブリを検索することで、呼び出し元に対して、実行時にロードされたアセンブリが、コードをコンパイルしてアセンブリをビルドするときに使ったものと同じ公開元からきていることがわかります。AssemblyRefテーブルは参照されているアセンブリのAssemblyDefの公開キーに対応しています。参照されているアセンブリがGACにない場合、CLRはアプリケーションのベースディレクトリとアプリケーションの構成ファイルで指定された任意のプライベートパスを検索します。さらに、アプリケーションがMSIを使ってインストールされた場合は、CLRはMSIに対してアセンブリを見つけるように指示します。アセンブリがこれらすべての場所で見つからなかった場合は、バインドは失敗してSystem.IO.FileNotFoundException例外がスロー(通知)されます。

厳密名付きアセンブリのファイルがGAC以外の場所から(構成ファイルのcodeBase要素経由で)ロードされた場合は、CLRはアセンブリがロードされるときにハッシュ値を比較します。言い換えると、アプリケーションを実行するたびにファイルのハッシュが行われるということです。パフォーマンスは劣化しますが、これはアセンブリファイルの内容が改ざんされていないことを保証するために必要です。CLRが実行時にハッシュ値の不一致を検出すると、System.IO.FileLoadException例外がスローされます。

3.6 | 遅延署名

本章の最初の方で、SN.exeを使って公開キー/秘密キーのペアを作成する方法を解説しました。SN.exeは、Windowsが提供するCryptoAPIを使ってキーを生成しています。このキーは、ファイルやほかの「記憶装置」に保管することができます。たとえば、Microsoftのような大きな企業では、生成された秘密キーをハードウェアデバイスに保管して、金庫に保管しています。社内でもごく一部の人だけが秘密キーにアクセスできます。このように用心することで、秘密キーが盗まれるのを防ぎ、キーの整合性を保っています。公開キーは、文字どおり公開されており、自由に配布できます。

厳密名付きアセンブリをパッケージングする準備ができたら、秘密キーで署名しなければなりません。しかし、アセンブリの開発中やテスト中に、秘密キーにアクセスするのはわずらわしいことがあります。このため、.NET Frameworkでは「遅延署名」(「部分署名」とも呼ばれます)をサポートしています。

遅延署名機能によって、企業の公開キーだけを使ってアセンブリをビルドできるようになります。秘密キーは必要ありません。公開キーは使えるので、開発中のアセンブリを参照するほかのアセンブリは、正しい公開キーを自分のAssemblyRefメタデータエントリに埋め込むことができます。また、GACの内部構造にも正しく配布することができます。秘密キーで署名していない場合、すべての改ざん防止機能が機能しません。アセンブリはハッシュされませんし、デジタル署名もファイルに埋め込まれないからです。ただ、この機能がないことはたいして問題にはなりません。遅延署名機能はアセンブリの開発中に使うものであって、パッケージングして配布するときに使う機能ではないからです。

基本的には、企業の公開キーをファイルとして入手し、そのファイル名をアセンブリのビルドツールに渡せばいいだけです(SN.exeの-pスイッチで、公開キー/秘密キーのペアのファイルから公開キーを取り出すことができます)。また、ツールに対して遅延署名をすること、つまり秘密キーは提供しないことを指示する必要もあります。ソースコードで設定する場合は、AssemblyKeyFileAttributeおよびAssemblyDelaySignAttributeの2つの属性でそれを指定します。AL.exeを使う場合は、**/keyf[ile]および/delay[sign]**スイッチを指定します。

コンパイラやAL.exeがアセンブリの遅延署名指定を検出すると、アセンブリのAssemblyDefエントリが出力されます。このエントリには公開キーが設定されています。繰り返しますが、公開キーが存在するので、このアセンブリをGACに入れることができます。また、このアセンブリを参照するほかのアセンブリを作成することもできます。そのアセンブリのAssembyRefメタデータテーブルのエントリにも正しい公開キーが設定されます。アセンブリが出力されると、結果のPEファイルにはRSAデジタル署名が入るスペースが確保されます(公開キーのサイズから、必要なスペースの大きさを計算することができます)。この時点ではファイルの内容のハッシュも行われていないことに注意してください。

この時点では、出力されたアセンブリには正当な署名がされていません。このアセンブリをGACにインストールしようとすると失敗します。ファイルの内容のハッシュが行われていないので、改ざんされているかのように見えてしまうからです。このアセンブリをGACにインストールするには、このアセンブリのファイルの整合性確認を一時的に止めるようにシステムを設定します。これは、SN.exeに-Vrコマンドラインスイッチを指定することで設定できます。このスイッチを指定してSN.exeを実行すると、CLRに対しても、実行時にロードするときには、このアセンブリのすべてのファイルに対してハッシュ値の確認をしないようにすることができます。

アセンブリの開発とテストが終了後、パッケージングと配布のために公式に署名をしなければなりません。アセンブリに署名するには、再びSN.exeを、今回は-Rスイッチと本物の秘密キーを持っているファイル名を指定して起動します。SN.exeの-Rスイッチは、ファイルの内容をハッシュして、秘密キーで署名し、RSAデジタル署名を、ファイルのあらかじめ確保されていた空間に書き込みます。この手順が終わったら、この完全に署名されたアセンブリを配布することができるようになります。このアセンブリに対する検証を再度有効にするには、SN.exeの-Vuスイッチか-Vxスイッチを利用します。

遅延署名テクニックを使ってアセンブリを開発する手順をまとめると、次のようになります。

  1. 開発中に利用する、企業の公開キーだけを持つファイルを入手します。また、ソースコードに次の2つの属性を設定します。

    [assembly:AssemblyKeyFile("MyCompanyPublicKey.snk")] [assembly:DelaySign(true)]

  2. アセンブリをビルド後、次のコマンドを実行して、GACへのインストール、ほかのアセンブリからの参照、アセンブリのテストを可能にします。これは1度だけ行えばいいので注意してください。アセンブリをビルドするたびに行う必要はありません。

    SN.exe -Vr MyAssembly.dll

  3. アセンブリのパッケージと配布の準備ができたら、企業の秘密キーを入手して次のコマンドを実行します。

    SN.exe -R MyAssembly.dll MyCompanyPrivateKey.snk

  4. テストするために、次のコマンドを実行して検証機能を再度有効にします。

    SN.exe -Vu MyAssembly.dll

この節の冒頭で、企業が自社のキーペアを守るために、スマートカードなどのハードウェアデバイスに保管する話を取り上げました。このキーを安全に保つためには、ディスク上にファイルとして置いてはいけません。暗号サービスプロバイダ(Cryptographic service providers:CSP)が、このキーのありかを抽象化する「コンテナ」を提供しています。たとえば、Microsoftは、アクセスに応じてスマートカードから秘密キーを取得するコンテナを持つCSPを使っています。

公開キー/秘密キーのペアがCSPのコンテナに入っている場合は、AssemblyKeyFileAttribute属性やAL.exeの**/keyf[ile]スイッチは使いません。代わりに、System.Reflection.AssemblyKeyNameAttribute属性か、AL.exeの/keyn[ame]スイッチを使います。SN.exeを使って秘密キーを遅延署名されたアセンブリに追加するときは、-Rスイッチの代わりに-Rc**スイッチを使います。SN.exeは、ほかにもCSPと連携して操作を行うスイッチを提供しています。

重要 遅延署名は、アセンブリに配布前に手を加えたいときにはどのような場合でも便利に利用できます。たとえば、アセンブリは単なるWindowsのPEファイルなので、ファイルのロードアドレスを再設定することもできます。これは、Microsoft Win32 Platform SDKに付属しているRebase.exeを利用して行うことができます。ファイルに完全に署名されてしまうと、ロードアドレスを変更することはできなくなります。これは、ハッシュ値が変わってしまうからです。したがって、ロードアドレスの再配布に限らず、ビルド後に行う操作をしたい場合は、まず遅延署名をしてアセンブリをビルドします。その後にビルド後の操作を行って、最後にSN.exeに-Rスイッチか-Rcスイッチを指定して、アセンブリに対するすべてのハッシュと署名を行えばよいでしょう。

筆者が個人的にすべてのプロジェクトで使っているAssemInfo.csファイルを次に紹介します。

/***************************************************************************** Module: AssemInfo.cs Notices: Copyright (c) 2002 Jeffrey Richter *****************************************************************************/ using System.Reflection; ////////////////////////////////////////////////////////////////////////////// // バージョン情報のCompanyName、LegalCopyright、LegalTrademarksフィールド // にセットする [assembly:AssemblyCompany("The Jeffrey Richter Company")] [assembly:AssemblyCopyright("Copyright (c) 2002 Jeffrey Richter")] [assembly:AssemblyTrademark("JeffTypes はRichter Company の登録商標です。")] ////////////////////////////////////////////////////////////////////////////// // バージョン情報のProductNameとProductVersionフィールドにセットする [assembly:AssemblyProduct("Jeffrey Richter Type Library")] [assembly:AssemblyInformationalVersion("2.0.0.0")] ////////////////////////////////////////////////////////////////////////////// // バージョン情報のFileVersion、AssemblyVersion、 // FileDescription、Commentsフィールドにセットする [assembly:AssemblyFileVersion("1.0.0.0")] [assembly:AssemblyVersion("3.0.0.0")] [assembly:AssemblyTitle("Jeffの型アセンブリ")] [assembly:AssemblyDescription("このアセンブリにはJeffの型が入っています。")] ////////////////////////////////////////////////////////////////////////////// // cultureをセットする(""はneutralを表す) [assembly:AssemblyCulture("")] ////////////////////////////////////////////////////////////////////////////// #if !StronglyNamedAssembly // あいまいな名前のアセンブリは署名されない [assembly:AssemblyDelaySign(false)] #else // 厳密名付きアセンブリは通常の開発中は遅延署名されて // 後でSN.exeの-Rか-Rcスイッチを使って完全に署名される [assembly:AssemblyDelaySign(true)]     #if !SignedUsingACryptoServiceProvider          // 公開キー/秘密キーのペアが入っているファイル名を指定する     // 遅延署名の場合は公開キーだけが使われる。     [assembly:AssemblyKeyFile("MyCompany.snk")]     // 注:AssemblyKeyFileとAssemblyKeyNameが両方指定された場合は     // 次のいずれかが起こる...     // 1) コンテナが存在すれば、キーファイルは無視される     // 2) コンテナがない場合は、キーファイルがコンテナにコピーされて     // アセンブリが署名される          #else          // 公開キー/秘密キーのペアが入っている     // 暗号化サービスプロバイダ(CSP)のコンテナ名を指定する     // 遅延署名の場合は公開キーだけが使われる。     [assembly:AssemblyKeyName("")]     #endif #endif //////////////////////////////// End of File /////////////////////////////////

Visual Studio .NETで新しいプロジェクトを作成すると、自動的に上記のファイルの内容とほとんど同じAssemblyInfo.csファイルが作成されます。筆者が上記のファイルを利用する理由の1つは、その設定によって何が起きるのか、どのようにバージョンリソース情報にマップされるかを、より正確にコメントに記述しているからです。さらに、Visual Studio .NETのAssemblyInfo.csファイルは、AssemblyVersion属性に「1.0.*」という誤った値を設定しているため、CSC.exeがビルドを行うたびに新しいビルドとリビジョン番号を生成してしまいます。ビルドのたびにアセンブリのバージョンが変わってしまうと、古いバージョン番号でそのアセンブリを参照している既存のアセンブリからロードされたときに、CLRが正しくアセンブリをロードできません。

Visual Studio .NETのAssemblyInfo.csファイルには、System.Reflection.Assembly Configuration属性が含まれていますが、この属性はCLRでは使われなくなっているので、.NET Frameworkから完全に削除されるべきものでした。この属性をファイルに残しておくのは単なる無駄です。

3.7 | 厳密名付きアセンブリをプライベートに配布する

アセンブリをGACにインストールすることは、いくつかのメリットがあります。GACによって多数のアプリケーションがアセンブリを共有できるので、物理的なメモリ利用を削減できます。さらに、アセンブリの新しいバージョンをGACに配布して、発行者ポリシー(後述)を使ってすべてのアプリケーションに新しいバージョンを使わせることも簡単にできます。GACを使って、異なるバージョンのアセンブリのサイドバイサイド管理もできます。しかし、GACは通常セキュリティで保護されているため、管理者でなければアセンブリをインストールできません。また、GACにインストールするという行為によって、「コピーだけで配布できる」というメリットは失われてしまいます。

厳密名付きアセンブリはGACにインストールできますが、必ずしなければいけないわけではありません。実は、アセンブリが多くのアプリケーションで共有される場合に限って、GACに配布することが推奨されています。アセンブリが共有されないのであれば、プライベートに配布すべきです。プライベートに配布すれば、「単にコピーだけ」のインストールと配布が実現され、アプリケーションとアセンブリを隔離できます。まして、GACは新しいC:¥Windows¥System32(共通ファイル用の「ゴミ捨て場」)となることを目的に作成されたわけではありません。この理由は、GACでは新しいバージョンのアセンブリが古いバージョンを上書きしないからです。両者はサイドバイサイドでインストールされ、ディスクスペースを消費します。

厳密名付きアセンブリは、GAC内またはプライベートに配布するだけではなく、少数のアプリケーションだけが知るほかの任意のディレクトリに配布することもできます。たとえば、1つのアセンブリを共有する3つのアプリケーションを作成しているとします。その場合、インストールするときに、アプリケーションごとに1つずつディレクトリを作成します。そしてもう1つ共有アセンブリ用のディレクトリを作成します。それぞれのアプリケーションをディレクトリにインストールするときに、同時にXMLの構成ファイルもインストールして、その中で共有アセンブリのcodeBase要素に共有アセンブリへのパスを記述しておきます。すると、実行時にCLRは共有アセンブリを探すために、(4つ目の)厳密名付きアセンブリのディレクトリを見に行くようになります。念のため付け加えますが、このテクニックはほとんど使われなく、推奨もされません。この理由は、どのアプリケーションからもアセンブリのファイルのアンインストールを制御できないからです。

注 構成ファイルのcodeBase要素は、実はURLを表します。このURLはユーザーのハードディスク上の任意のディレクトリを指すことができるほか、Web上のアドレスを指すこともできます。Webアドレスの場合、CLRはファイルを自動的にダウンロードして、ユーザーのダウンロードキャッシュ(C:¥Documents and Settings¥<ユーザー名>¥Local Settings¥Application Data¥Assemblyの下のサブディレクトリ)に保存します。次に同じアセンブリがアクセスされたときは、CLRはURLにはアクセスせずに、このディレクトリからアセンブリをロードします。codeBase要素を含む構成ファイルの例は、本章でこの後で登場します。
注 厳密名付きアセンブリがGACにインストールされるときに、マニフェストを持っているファイルが改ざんされていないかどうか、システムがチェックします。このチェックはインストール時に1度だけ行われます。一方、厳密名付きアセンブリがGAC以外のディレクトリからロードされたときは、CLRはアセンブリのマニフェストを検証して、ファイルの内容が改ざんされていないことを確認します。これはファイルがロードされるたびに毎回行われるので、パフォーマンス上のオーバーヘッドになります。

3.8 | サイドバイサイド実行

ここまで説明してきた強力なバージョン管理機能を使うと、App.exeというアセンブリが、バージョン2.0.0.0のCalculus.dllアセンブリと、バージョン3.0.0.0のAdvMath.dllアセンブリにバインドしたうえに、AdvMath.dllアセンブリがバージョン1.0.0.0のCalculus.dllにバインドすることができます。図3-5を見てください。

図3-5 実行するために異なるバージョンのCalculus.dllアセンブリを必要とするアプリケーション

▲図3-5 実行するために異なるバージョンのCalculus.dllアセンブリを必要とするアプリケーション

CLRには単一のアドレス空間に、異なるパスから同じ名前のファイルを複数ロードする機能があります。これを「サイドバイサイド実行」と呼びます。これはWindowsの「DLL地獄」問題を解決するためのキーとなる機能です。

重要 DLLのサイドバイサイド実行はすばらしい機能です。これによって、新しいバージョンのアセンブリに下位互換性がなくてもよくなったのです。下位互換性がなくてもよいのであれば、製品のコーディングとテストの時間を削減できるので、製品を市場により早く送り出すことができるようになります。

捕らえにくいバグが入り込まないように、開発者はこのサイドバイサイド実行のメカニズムを知っておかなければなりません。たとえば、アセンブリは名前付きのWin32ファイルマップカーネルオブジェクトを作成して、このオブジェクトの記憶領域を利用することができます。もう1つのバージョンのアセンブリがロードされて、同じ名前のファイルマップオブジェクトを作ろうとします。しかし、2つ目のアセンブリは新しい記憶領域を入手することはできず、最初のアセンブリが確保した記憶領域にアクセスしてしまいます。慎重にコーディングしないと、2つのアセンブリはお互いのデータを踏みにじりあって、アプリケーションの動作は予測できないものになるでしょう。

3.9 | ランタイムによる型の参照の解決

第2章の冒頭で、次のコードを取り上げました。

public class App {     static public void Main(System.String[] args) {         System.Console.WriteLine("Hi");     } }

このコードはコンパイルされ、App.exeというアセンブリとしてビルドされました。このアプリケーションを実行すると、CLRがロードされて初期化されます。次に、CLRはアセンブリのCLRヘッダーで、アプリケーションのエントリポイントメソッド(Main)を示すMethodDefTokenを検索します。そして、MethodDefメタデータテーブルから、このファイル内における、メソッドのILコードへのオフセットが取り出されます。続いて、このILコードはJITコンパイルされます。この過程で型安全性の検証も行われます。そして、ネイティブコードの実行が開始されます。MainメソッドのILコードは次のようなものです。これを取り出すには、ILDasm.exeを実行して、[表示]メニューの[バイト数の表示]を選択して、それからツリービューのMainメソッドをダブルクリックします。

.method public hidebysig static void  Main(string[] args) cil managed // SIG: 00 01 01 1D 0E {   .entrypoint   // Method begins at RVA 0x2050   // Code size       11 (0xb)   .maxstack  1   IL_0000:  /* 72   | (70)000001       */ ldstr      "Hi"   IL_0005:  /* 28   | (0A)000002       */  call       void [mscorlib]System.Console::WriteLine(string)   IL_000a:  /* 2A   |                  */ ret } // end of method App::Main

CLRは、このコードをJITコンパイルする過程で、型とメンバへの参照をすべて検出し、(まだロードされていなければ)それらを定義しているアセンブリをロードします。見てわかるとおり、上記のILはSystem.Console.WriteLineを参照しています。特に、ILのcall命令がメタデータトークンの0A000002を参照していることに注目してください。このトークンはMemberRefメタデータテーブルのエントリを表しています。CLRはこのMemberRefエントリを調べて、あるフィールドがTypeRefテーブル(System.Console型)のエントリを参照していることを突き止めます。CLRはTypeRefエントリからさらに、"MSCorLib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" というAssemblyRefエントリを調べます。その結果、CLRはどのアセンブリを検索してロードする必要があるのかがわかります。

CLRは、参照されている型を、次の3つのうちのいずれかから見つけることができます。

  • 同じファイル
    同じファイルにある型へのアクセスは、コンパイル時に判別されます(事前バインドと呼ばれます)。型はファイルから直接ロードされ、実行は継続します。
  • 同じアセンブリの異なるファイル
    参照されているファイルが、実は現在のアセンブリのマニフェストのFileRefテーブルにあった場合、CLRはマニフェストを持っているアセンブリのファイルがロードされたディレクトリを検索します。そしてファイルがロードされ、ハッシュ値をチェックして整合性が確認され、型のメンバが見つかったら、実行が継続されます。
  • 異なるアセンブリの異なるファイル
    参照されている型が異なるアセンブリのファイルに入っている場合、CLRは参照しているアセンブリのマニフェストを持っているファイルをロードします。このファイルに型が含まれていない場合には、適切なファイルがロードされます。型のメンバが見つかったら、実行が継続されます。
注 ModuleDef、ModuleRefおよびFileDefメタデータテーブルは、ファイル名と拡張子でファイルを参照しています。しかし、AssemblyRefメタデータテーブルは、アセンブリを拡張子なしのファイル名だけで参照しています。アセンブリにバインドする場合、システムが自動的に.dllと.exe拡張子をファイルに追加して、第2章の「2.6 シンプルなアプリケーション配布(プライベートアセンブリ)」の中の節で解説した、ディレクトリのプローブを行います。

型への参照の解決の最中になんらかのエラー(ファイルが見つからない、ファイルがロードできない、ハッシュが合わないなど)が発生した場合には、適切な例外がスローされます。

上記の例では、CLRはSystem.Consoleが呼び出し元とは異なるアセンブリで実装されていることを発見します。CLRはアセンブリを検索して、アセンブリのマニフェストを持つPEファイルをロードしなければなりません。そしてマニフェストが走査されて、型の実装が入っているPEファイルを判別します。マニフェストを持つファイルが参照されている型も持っていた場合は、ほかに何もすることはありません。型がアセンブリの別のファイルに入っている場合には、CLRはそのファイルをロードし、メタデータを走査して型を検索します。その後CLRは型を表現する内部的なデータ構造を作成し、JITコンパイラがMainメソッドのコンパイルを終了します。これでようやく、Mainメソッドの実行が始まります。

図3-6は型のバインドがどのように起こるかを示しています。

図3-6 ILコードが型やメソッドを参照しているとき、CLRがその型を定義している適切なアセンブリのファイルを見つけるために、どのようにメタデータを利用するかを示すフローチャート

▲図3-6 ILコードが型やメソッドを参照しているとき、CLRがその型を定義している適切なアセンブリのファイルを見つけるために、どのようにメタデータを利用するかを示すフローチャート

重要 厳密に言うと、この例は100%正確ではありません。MSCorLib.dll以外のアセンブリで定義されている型については正しいのですが、MSCorLib.dllだけは実行中のCLRと緊密に結合されています。MSCorLib.dll(ECMAの公開キートークンを表す「b77a5c561934e089」が付いています)を参照するすべてのアセンブリは、CLR自身と同じディレクトリにあるMSCorLib.dllにバインドします。つまり上記の例では、System.ConsoleWriteLineメソッドへの参照は、CLRのバージョンにマッチするバージョンのMSCorLib.dllにマッチします。アセンブリのAssemblyRefメタデータテーブルにMSCorLib.dllのどのようなバージョンが記述されていても関係ありません。

ここでは、CLRがデフォルトのポリシーを使ってアセンブリを見つけ出す方法を説明しました。しかし、管理者やアセンブリの公開元がこのデフォルトポリシーを変更することができます。次の2つの節で、CLRのデフォルトのバインディングポリシーを変更する方法を説明しましょう。

3.10 | 高度な管理作業(構成)

第2章の「2.7 シンプルな管理作業(構成)」では、CLRがバインドするアセンブリを探す手順を管理者が変更する方法を、簡単に紹介しました。参照されているアセンブリのファイルを、アプリケーションのベースディレクトリの下にあるサブディレクトリに移動した後、移動したファイルを見つけるために、CLRがアプリケーションのXML構成ファイルをどのように使うのかを解説しました。

第2章ではまだprobing要素のprivatePath属性の説明しかしていないので、ここではXML構成ファイルのほかの要素について説明します。XML構成ファイルは次のようなものを使います。

<?xml version="1.0" encoding="utf-8" ?> <configuration>    <runtime>       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">          <probing privatePath="AuxFiles;bin\subdir" />          <dependentAssembly>             <assemblyIdentity name="JeffTypes"                publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>             <bindingRedirect                oldVersion="1.0.0.0" newVersion="2.0.0.0" />             <codeBase version="2.0.0.0"                href="http://www.Wintellect.com/JeffTypes.dll" />          </dependentAssembly>          <dependentAssembly>             <assemblyIdentity name="FredTypes"                publicKeyToken="1f2e74e897abbcfe" culture="neutral"/>             <bindingRedirect                oldVersion="3.0.0.0-3.5.0.0" newVersion="4.0.0.0" />             <publisherPolicy apply="no" />          </dependentAssembly>       </assemblyBinding>    </runtime> </configuration>

このXMLファイルは、CLRに対して多くの情報を提供しています。このファイルの内容には次のような意味があります。

  • probing要素
    CLRがあいまいな名前のアセンブリを検索するときに、アプリケーションのベースディレクトリの下にある、AuxFilesとbin¥subdirサブディレクトリを検索するようにします。厳密名付きアセンブリの場合は、CLRはGACかcodeBase要素のURLを使って検索します。CLRが厳密名付きアセンブリを検索するときは、codeBase要素が指定されていないときに限って、アプリケーションのプライベートパスを検索します。
  • 1つ目のdependentAssembly、assemblyIdentity、bindingRedirect要素
    バージョン1.0.0.0で、ニュートラルカルチャ、32ab4ba45e0a69a1という公開キートークンを管理している組織が作成したJeffTypesというアセンブリを検索することになった場合には、代わりにバージョン2.0.0.0を検索させます。
  • codeBase要素
    バージョン2.0.0.0で、ニュートラルカルチャで、32ab4ba45e0a69a1という公開キートークンを管理している組織が作成したJeffTypesというアセンブリを検索するときには、http://www.Wintellect.com/JeffTypes.dllというURLを検索するようにCLRに指示します。第2章では触れませんでしたが、codeBase要素はあいまいな名前のアセンブリに対しても使えます。この場合は、アセンブリのバージョン番号は無視されるので、XMLのcodeBase要素からは除いた方がいいでしょう。また、あいまいな名前のアセンブリの場合にはcodeBaseURLはアプリケーションのベースディレクトリの下を指していなければなりません。
  • 2つ目のdependentAssembly、assemblyIdentity、bindingRedirect要素
    ニュートラルカルチャで、1f2e74e897abbcfeという公開キートークンを管理している組織が作成したFredTypesというアセンブリのバージョン3.0.0.0以上3.5.0.0以下を検索することになった場合には、代わりにバージョン4.0.0.0を検索させます。
  • publisherPolicy要素
    FredTypesアセンブリを提供している組織が発行者ポリシー(次の節で解説)を配布している場合でも、CLRにはその発行者ポリシーを無視させます。

メソッドをコンパイルするときに、CLRは参照されている型とメンバを判別します。ランタイムはこの情報を使って、呼び出し元のアセンブリがビルドされたときに、もともとどのようなアセンブリを参照していたのかを(呼び出し元のアセンブリのAssemblyRefテーブルを使って)判断します。CLRは次にアプリケーションの構成ファイルでこのアセンブリの設定を見て、バージョン番号のリダイレクトがあれば、それを適用します。

publisherPolicy要素のapply属性にyesが設定されている場合、または要素そのものがない場合は、CLRはGACを検索して、アセンブリの公開元が必要としているバージョン番号のリダイレクトを適用します。発行者ポリシーについては次の節でより詳しく説明します。

CLRは次に、コンピュータのMachine.configファイルからこのアセンブリの設定を検索して、バージョン番号のリダイレクトがあれば、それを適用します。これでようやく、アセンブリのどのバージョンをロードすればいいのかが理解できたので、CLRはアセンブリをGACからロードしようとします。アセンブリがGACに登録されておらず、codeBase要素もない場合は、CLRは第2章で解説したアセンブリのプローブを行います。最後にリダイレクトを行った構成ファイルがcodeBase要素を持っていれば、CLRはそのURLからアセンブリをロードしようとします。

管理者はこれらの構成ファイルを使って、CLRがロードするアセンブリを完全に制御することができます。アプリケーションがバグに遭遇した場合は、管理者はそのアセンブリの公開元に連絡します。公開元は、管理者に新しいアセンブリを送ります。デフォルトでは、CLRはこの新しいアセンブリをロードしません。既にビルド済みのアセンブリが、この新しいバージョンを参照していないからです。しかし、管理者がアプリケーションのXML構成ファイルを修正して、CLRに新しいアセンブリをロードするように指示することができます。

コンピュータ上のすべてのアプリケーションが新しいアセンブリを使うようにしたい場合は、管理者はMachine.configファイルを修正すれば、古いアセンブリが参照されたときにはいつも新しいアセンブリが代わりにロードされるようにすることができます。

新しいアセンブリでも、元のバグが修正されていなかった場合は、管理者は構成ファイルからbindingRedirectの行を削除すれば、アプリケーションは古いアセンブリのままで動作するようになります。メタデータに記録されているバージョンと正確に一致しないバージョンのアセンブリをロードできるということは、重要なポイントです。この柔軟性は非常に便利です。本章で後ほど、管理者が簡単にアプリケーションを復元できる機能についてもう少し詳しく説明します。

.NET Framework Configurationツール

XMLのテキストファイルを手動で編集したくない場合は(手動で編集したい人がいるのでしょうか?).NET Frameworkに付属の.NET Framework Configurationツールを使うことができます。コントロールパネルから[管理ツール]を選択し、[Microsoft .NET Framework Configuration]を選択します。ツールが起動したら、左側のウィンドウ領域にあるツリービューで、[マイコンピュータ]の下にある[アプリケーション]を選択します。ここで右側のウィンドウ領域から[構成するアプリケーションの追加]をクリックして、アプリケーションを選択します。ツリービューにアプリケーションが追加されるので、その下にある[アセンブリの構成]を選択して、[アセンブリを構成する]をクリックします。表示されたダイアログボックスで構成したいアセンブリを選択すると、アセンブリのプロパティダイアログボックスが表示されます。このダイアログボックスから、XMLの構成情報をすべて設定することができます。図3-7、3-8、3-9はそれぞれ、このプロパティダイアログボックスのページです。

図3-7  [System.Drawingのプロパティ]ダイアログボックスの[全般]タブ

▲図3-7  [System.Drawingのプロパティ]ダイアログボックスの[全般]タブ

図3-8 [System.Drawingのプロパティ]ダイアログボックスの[バインドポリシー]タブ

▲図3-8 [System.Drawingのプロパティ]ダイアログボックスの[バインドポリシー]タブ

図3-9[System.Drawingのプロパティ]ダイアログボックスの[コードベース]タブ

▲図3-9[System.Drawingのプロパティ]ダイアログボックスの[コードベース]タブ

3.10.1 | 発行者ポリシー制御

前節で説明したシナリオでは、公開元は単純に新しいバージョンのアセンブリを管理者に送り、管理者はアセンブリをインストールし、手動でアプリケーションやコンピュータのXML構成ファイルを編集していました。公開元からすれば、アセンブリのバグを修正したら、簡単にパッケージングしてすべてのユーザーに新しいアセンブリを配布できる方法が求められます。さらに、各ユーザーが使っているCLRに対して、古いバージョンの代わりに新しいバージョンのアセンブリを使うように指示する方法も必要です。確かに、各ユーザーもアプリケーションやコンピュータの構成ファイルを編集できますが、それでは非常に不便で、エラーの元凶にもなります。公開元が必要としているのは、新しいアセンブリと共にユーザーのコンピュータにインストールできる「ポリシー情報」を作成する手段です。ここでは、アセンブリの公開元がこのポリシー情報を作成する方法を紹介します。

アセンブリの公開元が、いくつかのバグを修正した新しいバージョンのアセンブリを作成したとしましょう。新しいアセンブリをパッケージングしてすべてのユーザーに送るときには、一緒にXML構成ファイルも作成しておく方がいいでしょう。この構成ファイルの内容は、先ほどまで見てきたものとまったく同じです。次のファイル(JeffTypes.configという名前だとします)が、JeffTypes.dllアセンブリ用のファイルです。

<configuration>    <runtime>       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">          <dependentAssembly>             <assemblyIdentity name="JeffTypes"                publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>             <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />             <codeBase version="2.0.0.0"                href="http://www.Wintellect.com/JeffTypes.dll" />          </dependentAssembly>       </assemblyBinding>    </runtime> </configuration>

当然ですが、公開元が設定できるポリシーは、自社が提供するアセンブリに対するものだけです。また、発行者ポリシーで設定できるのは上記の要素だけです。たとえば、probingpublisherPolicy要素は指定できません。

この構成ファイルでは、CLRに対してバージョン1.0.0.0のJeffTypesアセンブリが参照されたときは、バージョン2.0.0.0をロードするように指示しています。公開元は、この発行者ポリシーを含むアセンブリを作成することができます。発行者ポリシーアセンブリは、AL.exeを使って次のようにして作成します。

AL.exe /out:policy.1.0.JeffTypes.dll /version:1.0.0.0 /keyfile:MyCompany.snk /linkresource:JeffTypes.config

AL.exeのコマンドラインスイッチには、次のような意味があります。

  • /outスイッチ
    AL.exeが、マニフェストだけを含むpolicy.1.0.JeffTypes.dllという名前のPEファイルを作成するようにします。このアセンブリの名前はとても重要です。名前の最初の部分(policy)は、CLRに対してこのアセンブリが発行者ポリシー情報を持っていることを示します。2つ目と3つ目の部分(1.0)は、この発行者ポリシーアセンブリは、メジャーバージョンが1でマイナーバージョンが0のすべてのJeffTypesアセンブリに適用されることを表しています。発行者ポリシーはアセンブリのメジャーバージョンとマイナーバージョンに対してしか働きません。また、特定のビルドやリビジョン用の発行者ポリシーは作れません。4つ目の部分(JeffTypes)はこの発行者ポリシーが対応するアセンブリの名前です。5つ目の部分(dll)は単に出力されるアセンブリの拡張子です。
  • /versionスイッチ
    発行者ポリシーアセンブリのバージョンを指定します。このバージョン番号はJeffTypesアセンブリ自身とは何の関係もありません。発行者ポリシーアセンブリ自体もバージョン管理の対象になります。たとえば、今日はJeffTypesのバージョン1.0.0.0を2.0.0.0にリダイレクトする発行者ポリシーを作成しましたが、後でJeffTypesのバージョン1.0.0.0を今度は2.5.0.0にリダイレクトする発行者ポリシーを作成したくなるかもしれません。CLRはこのバージョン番号を使って最新の発行者ポリシーを見つけてくれます。
  • /keyfileスイッチ
    AL.exeが発行者ポリシーに公開元の秘密キー/公開キーのペアで署名できるようにします。このキーペアはすべてのバージョンのJeffTypesアセンブリの署名に使ったキーペアと同じでなければなりません。CLRはJeffTypesアセンブリの作成者と発行者ポリシーの作成者が同じであることを知るために、この情報を利用します。
  • /linkresourceスイッチ
    XML構成ファイルがアセンブリの一部として認識されるようにします。その結果、アセンブリは2つのファイルで構成されるようになります。両方のファイルがパッケージングされて、新しいバージョンのJeffTypesアセンブリと共にユーザーの手元に配布されなければなりません。余談ですが、XML構成ファイルを**/embedresource**スイッチを使って単一のファイルに埋め込むことはできません。CLRはXMLファイルがそれ自身別のファイルになっていることを必要とします。

発行者ポリシーがビルドできたら、新しいJeffTypes.dllアセンブリと共にパッケージングしてユーザーに配布します。発行者ポリシーアセンブリはGACにインストールされていなければなりません。JeffTypesアセンブリもGACにインストールできますが、こちらは必ずGACにインストールしなければならないものではありません。アプリケーションのベースディレクトリや、codeBaseURLで指定されているディレクトリに配布することもできます。

重要 公開元は、アセンブリのバグフィックスやサービスパックバージョンを配布するときだけ、発行者ポリシーアセンブリを作成するようにしてください。アプリケーションの初期導入時には、発行者ポリシーアセンブリは含めてはいけません。

発行者ポリシーに関して、最後に1点説明します。公開元が発行者ポリシーアセンブリを配布した後、なんらかの理由で新しいアセンブリが修正したものよりも多くのバグを新しく作り出していることがわかったとします。その場合、管理者はCLRに対して発行者ポリシーを無視するように指示できます。そのためには、管理者はアプリケーションの構成ファイルを変種して、次のpublisherPolicy要素を追加します。

<publisherPolicy apply="no"/>

この要素は、アプリケーションの構成ファイルの中ですべてのアセンブリに適用させることも、特定のアセンブリにだけ適用させることもできます。CLRは、アプリケーションの構成ファイルを処理した結果、GACで発行者ポリシーを検索しなくてもいいことがわかります。そのため、CLRはアセンブリの古いバージョンを使って動作し続けます。CLRは、たとえ上記の設定が行われていても、Machine.configで指定されたポリシーは無視せずに検証します。

重要 発行者ポリシーは、異なるバージョンのアセンブリの互換性に関する公開元の声明であると考えることができます。新しいバージョンのアセンブリが以前のバージョンとの互換性を考慮して作成されていない場合は、公開元は発行者ポリシーアセンブリを作成するべきではありません。通常は、バグが修正されたバージョンのアセンブリをビルドしたときに発行者ポリシーアセンブリを作成します。新しいバージョンのアセンブリに対して、下位互換性テストをすべきです。一方、アセンブリに新しい機能を追加した場合は、そのアセンブリは以前のバージョンとは何の関係もないものと考えて、発行者ポリシーアセンブリを配布すべきではありません。この場合は、アセンブリに対して下位互換性テストをする必要もありません。

3.11 | 欠陥アプリケーションの復元

コンソールアプリケーション、またはWindowsフォームベースのアプリケーションが、ログオンユーザーによって起動されると、CLRはアプリケーションが実際にロードしたアセンブリを記録します。ASP.NETのWebフォームやXML Webサービスのアプリケーションの場合は、記録されません。このアセンブリのロード情報はメモリ上に蓄積され、アプリケーションが終了した時点でディスクにファイルとして書き込まれます。この情報が書き込まれたファイルは次のディレクトリにあります。

C:\Documents and Settings\<ユーザー名>\Local Settings\Application Data\ApplicationHistory

<ユーザー名>の部分には、ログオンしているユーザーの名前が入ります。このディレクトリには、次のようなファイルがあります。

  ドライブ C のボリューム ラベルがありません。   ボリューム シリアル番号は D0E7-97DE です  C:\Documents and Settings\fumiakiy\Local Settings\Application Data\ApplicationHistory のディレクトリ  2002/04/03 17:00                .       2002/04/03 17:00                      ..             2002/04/03 17:00 532 App.exe.82182b0c.ini             2002/04/03 17:01 3,713 ConfigWizards.exe.2bfd2d4d.ini             2002/03/31 19:45 13,789 devenv.exe.7dc18209.ini             2002/04/03 14:40 4,277 mmc.exe.959a7e97.ini                           4 個のファイル               22,311 バイト                           2 個のディレクトリ   11,849,199,616 バイトの空き領域

個々のファイルが、特定のアプリケーションを識別します。16進の数値はファイルのパスのハッシュ値です。この値は、別々のサブディレクトリからロードされた同じ名前のファイルを区別するためにが必要です。

CLRは、アプリケーションが実行中にロードしたアセンブリの「スナップショット」を管理します。アプリケーションが終了すると、この情報は対応するアプリケーションの.iniファイルと比較されます。前回と同じセットのアセンブリがロードされていた場合には、ファイルの内容とメモリ上の情報が一致するはずなので、メモリ上の情報は破棄されます。内容が異なっていた場合は、CLRはメモリ上の情報を.iniファイルに追加します。デフォルトでは、.iniファイルにはスナップショットを5つまで保持できます。

基本的には、CLRはアプリケーションが使ったアセンブリの記録を取るだけです。さてここで、アセンブリの新しいバージョンと対応する発行者ポリシーをインストールしたとしましょう。1週間後、アプリケーションを実行してみたところ、突如としてアプリケーションが正しく動作しなくなりました。これまでWindows上でこのような問題が起きたときの最良の手順は、動かなくなったアプリケーションを再インストールして、そのほかのアプリケーションを壊さずに(実際にはよく壊れてしまっていました)システムが元に戻ることを期待することでした。

さいわいにも、CLRはエンドユーザーのためにアプリケーションが使っているアセンブリの履歴を管理しています。ユーザーがすべきことは、XML構成ファイルを作成して、アプリケーションが最後に正しく動いていた状態で利用していたアセンブリを使ってくれるように、CLRに指示するだけです。

.NET Framework Configurationツールを使えば、アプリケーションの構成ファイルの作成と編集を簡単に行えます。ツールを起動して、左側のウィンドウ領域にあるツリービューの[アプリケーション]を右クリックして、[アプリケーションの修正]を選択します。すると、図3-10のダイアログボックスが表示されます。

図3-10 .NET Application Configurationでアセンブリのロード情報の履歴を持っているアプリケーションを表示する

▲図3-10 .NET Application Configurationでアセンブリのロード情報の履歴を持っているアプリケーションを表示する

注 .NET Framework Configurationツールは、Microsoft管理コンソール(MMC)スナップインです。MMCはWindows 98、Windows 98 Second Edition、Windows Meにはインストールされていません。しかし、これらのオペレーティングシステムでは、.NET Framework Wizardsユーティリティを使うことができます。このツールは、[スタート]メニューの[プログラム]-[管理ツール]-[.NET Framework Wizards]から起動できます。

図3-10のダイアログボックスには、CLRがアセンブリのロード情報を蓄積しているアプリケーションが一覧表示されます。基本的には、ApplicationHistoryサブディレクトリの.iniファイルごとにエントリが表示されます。アプリケーションを選択して[OK]ボタンをクリックすると、図3-11のダイアログボックスが表示されます。

図3-11 .NET Application Configurationでロードされたアセンブリが変化した日付を表示する

▲図3-11 .NET Application Configurationでロードされたアセンブリが変化した日付を表示する

ダイアログボックスの個々のエントリは、アプリケーションがロードしたアセンブリのセットを表しています。ユーザーはアプリケーションが正しく動作していた日付範囲を選択すれば、ツールがアプリケーションのXML構成ファイルを編集して、その時点でロードされていたアセンブリのセットをCLRが再びロードするようにしてくれます。Application SafeModeエントリは、アプリケーションがビルドされてテストされたときに使われたものと、正確に同じアセンブリをロードする設定です。ランタイムが異なるバージョンのアセンブリにリダイレクトするのを防止します。

ツールが行ったアプリケーションのXML構成ファイルに対する変更は、「.NET Application Restore BeginBlock」と「.NET Application Restore EndBlock」で囲まれています。また、「NET Application Restore RollBackBlock」には、特定のスナップショットに復元する前のXML構成情報が含まれています。次のXMLはその一例です。

<configuration>    <runtime>       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">          <dependentAssembly>             <!--.NET Application Restore BeginBlock #1 29437104.-387708080                  8/24/2001 6:48:25 PM-->             <assemblyIdentity name="JeffTypes"                  publicKeyToken="32ab4ba45e0a69a1" culture="neutral" />             <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />             <publisherPolicy apply="no"/>             <!--.NET Application Restore EndBlock #1-->             <!--.NET Application Restore RollBackBlock #1                 8/24 2001 6:48:25 PM<assemblyIdentity name="JeffTypes"                 publicKeyToken="32ab4ba45e0a69a1" culture=""/>-->          </dependentAssembly>       </assemblyBinding>    </runtime> </configuration>