解説

インサイド .NET Framework [改訂版]

第5回 アセンブリのロードとセキュリティ

吉松 史彰
2003/07/30
Page1 Page2 Page3 Page4

CLRのロード

 mscoree.dllに入っている_CorExeMain(あるいは_CorDllMain)関数の仕事はあまり多くない。このDLLはshim(「詰め木」などの意)と呼ばれていて、実際の仕事はほとんどしない。_CorExeMain関数はすぐにmscorwks.dllかmscorsvr.dllを呼び出し、すべての処理はそこで行われる。どちらのDLLを呼び出すかを選択するのがmscoree.dllの_CorExeMain関数の役割だ。ほとんどの場合はmscorwks.dllがロードされる。マルチプロセッサのシステム上でプログラムが明示的に選択すれば、mscorsvr.dllを使うこともできる。mscorwks.dll/mscorsvr.dllがCLRの実体だといっていいだろう。

 mscorwks.dll/mscorsvr.dllは、%Systemroot%\Microsoft.NET\Framework\<バージョン>フォルダからロードされる。つまり、これらのファイル(CLRの本体)は、.NET Frameworkのバージョンごとに別々に存在するわけだ。しかし、これらをロードする役目を持つmscoree.dllは、%Systemroot%\System32に配置される。つまり、.NET Frameworkのバージョンに関係なく、マシンに1つしかないことになる。例えば、Windows Server 2003にはあらかじめ.NET Framework 1.1がインストールされている。そのマシンにあとから.NET Framework 1.0をインストールすると、%Systemroot%\Microsoft.NET\Framework\v1.0.3705フォルダが作成され、バージョン1.0のmscorwks.dll/mscorsvr.dllが新しくそこに配置される。しかし、mscoree.dllはすでにバージョン1.1のものが%Systemroot%\System32に配置されているので、バージョン1.0のものは配置されない。それでは、そのマシンで.NET Framework 1.0向けに開発されたEXEアセンブリが実行されようとしたときには、実際にロードされるCLRは1.0のものなのか、それとも1.1のものなのだろうか。

.NET Framework 1.1におけるCLRのサイドバイサイド実行(1)
.NET Framework 1.1がインストールされているマシンで1.0向けのアセンブリが実行される場合、実際にロードされるCLR(mscorwks.dll/mscorsvr.dll)はどちらのバージョンとなるのだろうか?

 mscoree.dllは、ロードするCLRのバージョンを特定する仕組みを2種類提供している。1つはアプリケーションがバージョンを明示する方法で、もう1つはCLRを起動するホスト・プロセスがバージョン番号を明示することである*1。後者は、今回解説しているデフォルトのロード機能を利用している場合は適用できない。例えば.NET Framework 1.0におけるaspnet_wp.exeがホスト・プロセスの例である(aspnet_wp.exeはIIS 5.0におけるASP.NET用のワーカープロセス。aspnet_wp.exeはCLRをホストし、ASP.NETのWebページを処理する)。後者を実現する場合は、ホスト・プロセスを開発して、CLRをロードするコードを自分で記述しなければならない。これ以降は、前者の仕組みだけを解説する。

*1 環境変数を設定する方法もあるが、あまり実践では使われないため、ここでは解説しない。

 アセンブリのメタデータには、そのアセンブリがコンパイルされた.NET Frameworkのバージョンが含まれている。コンパイルしたアセンブリのバイナリ・ファイルを、無理やりメモ帳で開いてみれば、中に「v1.0.3705」や「v1.1.4322」という文字列が見えるだろう。

 EXEファイルのアセンブリが起動されたときに、ほかに何も設定をしていない場合は、ここで指定されたバージョンで%Systemroot%\Microsoft.NET\Framework\<バージョン>のパスを置き換えて、mscorwks.dll/mscorsvr.dllをロードするフォルダが決定される。そのため通常は、.NET Framework 1.0向けにコンパイルされたアセンブリを利用する場合は、マシンにも.NET Framework 1.0をインストールしなければならない。同様に、.NET Framework 1.1のコンパイラでコンパイルされたアセンブリを使うには、.NET Framework 1.1をインストールしなければならない。

.NET Framework 1.1におけるCLRのサイドバイサイド実行(2)
アセンブリには、それがコンパイルされた.NET Frameworkのバージョンが含まれており、それに従ってロードされるCLRのバージョンが決定される。

 万が一、EXEアセンブリが対象にしている.NET Frameworkがインストールされていなかった場合には、ロードされるCLRのバージョンは、デフォルトでは次の表のようになる。

アセンブリ\.NET Framework 1.0のみ 1.1のみ
1.0でコンパイル v1.0.3705 v1.1.4322
1.1でコンパイル 動作しない v1.1.4322
アセンブリのバージョンとデフォルトでロードされるCLRのバージョン

 このように、.NET Framework 1.1向けにコンパイルされたEXEアセンブリが、.NET Framework 1.0しかインストールされていない環境で起動されると、エラーが発生して動作しない。一方、.NET Framework 1.0向けにコンパイルされたEXEアセンブリが、.NET Framework 1.1しかインストールされていない環境で起動された場合には、1.1のCLRがロードされる。この動作は、レジストリの設定によって実現されている。mscoree.dllは、要求されたバージョン(この場合はv1.0.3705)がマシンにインストールされていないことを発見すると、レジストリを開いてHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\Upgradesというキーを確認する。.NET Framework 1.1がインストールされている場合、デフォルトではこのキーには次のような値が設定されている。

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\Upgrades]
"1.1.4322"="1.0.3705-1.1.4322"

 つまり、1.0.3705から1.1.4322までのバージョンのCLRが要求された場合に、それを1.1.4322で代用することが許可されているのである。このため.NET Framework 1.1しかインストールされていない環境で.NET Framework 1.0向けにコンパイルされたアセンブリが実行できるようになっている。

 EXEアセンブリが起動した後に、さらに別のアセンブリを呼び出した場合はどうなるだろうか。例えば次のコードをコンパイルすると、mscorlibアセンブリとSystem.Dataアセンブリへの参照がメタデータに記述される。

class App {
  static void Main() {
    System.Data.DataSet ds = new System.Data.DataSet();
  }
}
System.DataアセンブリにあるDataSetクラスを使用するサンプル・コード
このコードをコンパイルすると、mscorlibアセンブリとSystem.Dataアセンブリへの参照がメタデータに記述される。

 アセンブリがコンパイルされるときに、対象となる参照先のアセンブリのバージョン番号がメタデータに記録されるため、デフォルトではバージョンが異なるアセンブリはロードできない。すでに解説したとおり、厳密名付きアセンブリはバージョン管理の対象になってサイドバイサイド実行されるのである。このため、このコードを.NET Framework 1.0の環境でコンパイルすると、次図のような参照関係を持つアセンブリが作成されることになる。

上記サンプル・コードのコンパイル時のアセンブリ参照
アセンブリがコンパイルされるときに、対象となる参照先のアセンブリのバージョン番号がメタデータに記録される。このためデフォルトでは、バージョンが異なるアセンブリはロードできない。

 従って、.NET Framework 1.1しかインストールされていない環境では動作しないように思える。

.NET Framework 1.0向けのアセンブリを1.1の環境で実行
.NET Framework 1.0向けのアセンブリは、.NET Framework 1.1しかインストールされていない環境では動作しないように思える。

 しかし、実際にはこのアセンブリは、.NET Framework 1.1しかインストールされていない環境で動作する。その秘密は、%Systemroot%\Microsoft.NET\Framework\v1.1.4322フォルダにあるfusion.dllにある。このDLLは、アセンブリをロードする機能を持つDLLで、これまで解説してきたアセンブリのロード手順を実装している。fusion.dllには「FXCONFIG」という名前付きリソースが含まれている。このリソースの中身は次のようなXML 1.0データである(抜粋)。

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System"
          publicKeyToken="b77a5c561934e089"
          culture="neutral" />
        <bindingRedirect
          oldVersion="0.0.0.0-65535.65535.65535.65535"
          newVersion="1.0.5000.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Configuration.Install"
          publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect
          oldVersion="0.0.0.0-65535.65535.65535.65535"
          newVersion="1.0.5000.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Data"
          publicKeyToken="b77a5c561934e089" culture="neutral" />
        <bindingRedirect
          oldVersion="0.0.0.0-65535.65535.65535.65535"
          newVersion="1.0.5000.0" />
      </dependentAssembly>
      …
fusion.dllに含まれている名前付きリソース「FXCONFIG」(抜粋)
内容はXML 1.0データで、アセンブリのバージョンをリダイレクトするための構成ファイルの情報が記述されている。

 すでに解説した、アセンブリのバージョンをリダイレクトするための構成ファイルの情報である。これによって、.NET Framework 1.0のアセンブリ(バージョン1.0.3300)への参照は、.NET Framework 1.1のアセンブリ(バージョン1.0.5000)へとリダイレクトされるので、.NET Framework 1.0のシステム・アセンブリを参照しているアセンブリが、.NET Framework 1.1しかインストールされていない環境で動作するのである。

fusion.dllによるアセンブリのリダイレクト
.NET Framework 1.0のアセンブリへの参照は、.NET Framework 1.1のアセンブリへとリダイレクトされる。このため、.NET Framework 1.0向けのアセンブリが、.NET Framework 1.1しかインストールされていない環境で動作する。

 もちろんこのような情報は、%Systemroot%\Microsoft.NET\Framework\v1.0.3705フォルダにあるfusion.dllには記述されていない。そのため、上記のDataSetクラスにアクセスするコードを.NET Framework 1.1の環境でコンパイルして、.NET Framework 1.0しかインストールされていない環境で実行しようとすると、エラーになる。.NET Framework 1.1は.NET Framework 1.0と互換性があることにはなっているものの、若干ながら動作が異なる機能や、.NET Framework 1.1で新しく用意された機能がある。これらの機能を利用している.NET Framework 1.1ベースのアプリケーションは、.NET Framework 1.0上では動作しないのは自明であろう。そのため、.NET Framework 1.1向けにコンパイルされたアセンブリは、デフォルトでは.NET Framework 1.0のみの環境では動作しない。

.NET Framework 1.1向けのアセンブリを1.0の環境で実行
.NET Framework 1.1は1.0と互換性があることにはなっているものの、動作が異なる機能や、1.1で新しく用意された機能がある。これらの機能を利用している1.1ベースのアプリケーションは、1.0上では動作しない。

 .NET Framework 1.1向けにコンパイルしたものの、.NET Framework 1.0でも動作することが確認できている場合は、対応可能なCLRのバージョンをアプリケーションの構成ファイルに記述して、古いバージョンの.NET Frameworkしかインストールされていないマシン上で実行できる機能がある。構成ファイルに次のように<startup>要素と<requiredRuntime>要素を記述する。

<configuration>
  <startup>
    <requiredRuntime version="v1.0.3705"/>
  </startup>
</configuration>
<requiredRuntime>要素を記述したアプリケーションの構成ファイル
アセンブリが対応可能なCLRのバージョンを構成ファイルに記述しておけば、古いバージョンの.NET Frameworkしかインストールされていないマシン上でも実行させることができる。

 このような構成ファイルがあると、.NET Framework 1.0のmscoree.dllは%Systemroot%\Microsoft.NET\Framework\v1.0.3705というフォルダからmscorwks.dll/mscorsvr.dllをロードしようとする。

 ただし、構成ファイルを上記のように記述したときに、.NET Framework 1.1用にコンパイルされたアセンブリから利用できるのは、.NET Framework 1.0のmscorlibアセンブリだけである。従って、このままでは上記のDataSetクラスにアクセスするコードはやはりエラーで実行できない。

<requiredRuntime>要素によるリダイレクト
アプリケーションの構成ファイルに記述した<requiredRuntime>要素での設定は、mscorlibアセンブリにのみ作用する。

 mscorlib以外のアセンブリを利用していても、互換性が確保されていることが確認できていれば、構成ファイルに<bindingRedirect>要素を追加して、「バージョンダウン」する記述をしなければならない。

<bindingRedirect>要素によるリダイレクト
mscorlib以外のアセンブリについては、<bindingRedirect>要素をアプリケーションの構成ファイルに記述して古いバージョンに対応させることができる。

 例えば、System.Dataアセンブリについても古いバージョンのものを使用するような構成ファイルは次のようになる。

<configuration>
  <startup>
    <requiredRuntime version="v1.0.3705"/>
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Data"
          publicKeyToken="b77a5c561934e089" culture="neutral" />
        <bindingRedirect
          oldVersion="0.0.0.0-65535.65535.65535.65535"
          newVersion="1.0.3300.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
<bindingRedirect>要素を記述したアプリケーションの構成ファイル
mscorlib以外の古いバージョンのアセンブリを使用するには、リダイレクトするアセンブリを個別に記述していく必要がある。

 現在の.NET Frameworkでは、CLRは1つのプロセスに1つしかロードできないため、最初に起動するEXEアセンブリ(またはホスト・プロセス)がロードするCLRのバージョンを決定することになる。CLRのバージョンが決定すると、mscorlibアセンブリのバージョンだけは自動的にCLRと同じバージョンのものに決定される。そのほかのアセンブリ(システムのアセンブリも含む)には、通常通りのバージョン・ポリシーが適用されることになる。

 なお、.NET Framework 1.1では、<supportedRuntime>要素が新たに追加されたが、いまのところこの要素の出番はない。この要素は.NET Framework 1.1のmscoree.dllしか解釈できないので、次の表のように、この要素を利用する場面はないからである。

アセンブリ\.NET Framework 1.0のみ 1.1のみ 1.0と1.1を両方インストール
1.0でコンパイル 1.0で動作 1.1で動作 1.0で動作
1.1でコンパイル <requiredRuntime>要素があれば1.0で動作可能。なければ動作しない。 1.1で動作 1.1で動作
アセンブリのバージョンと動作するCLRのバージョン
<requiredRuntime version="v1.0.3705" />と記述すれば、1.1では動作しないように設定することも可能
 

 INDEX
  解説 インサイド .NET Framework [改訂版]
  第5回 アセンブリのロードとセキュリティ
    1.Windowsローダーの動作
  2.CLRのロード
    3.署名の検証とプログラムの開始
    4.いくつものセキュリティ・チェック
 
インデックス・ページヘ  「解説:インサイド .NET Framework [改訂版]」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間