解説インサイド .NET Framework第5回 アセンブリのロード(後編) インフォテリア株式会社 |
前回では、サンプル・アプリケーションを実行する過程で、CLRがUtilクラスを実装しているアセンブリを識別し、プライマリ・モジュール(マニフェストが含まれるモジュール)を探索するまでの手順を解説した。今回はこれに続くアセンブリ・ロード手順の第3段階から話を始める。
− アセンブリのロード 第3段階 −
アセンブリのマニフェストの確認
アセンブリのプライマリ・モジュール(util.dllファイル)が見つかったので、CLRはそれをロードしてマニフェストを開く。CLRはまず、ロードしたアセンブリが本当に探していたアセンブリなのか、厳密名のすべてが一致するものであるかどうかを確認する。util.dllに入っているマニフェストには次のようなエントリがある。
util.dllのマニフェストにある、AssemblyDef(アセンブリ定義テーブル)のエントリ |
これは、AssemblyDef(アセンブリ定義テーブル)のエントリと呼ばれるデータだ。これを見れば、探していたアセンブリと一致するアセンブリかどうかが一目瞭然だ。万が一ロードしたマニフェストに書かれている内容が探していたアセンブリとは異なっている場合(例えばバージョンが違ったり、公開キーが違ったりする場合)は、“System.IO.FIleLoadException”が発生してロードは失敗したと見なされる。
− アセンブリのロード 第4段階 −
利用する型の探索
正しいアセンブリがロードできたので、今度はこのアセンブリに入っているはずの型を探す。そもそも本稿の説明は、すべて次のコードを実行しようとしたときにCLRが行う作業だ。
Util u = new Util();
つまり、今ロードしたアセンブリからUtil型の定義を取り出して、そのインスタンスをメモリ上に構築しようとしているわけだ。そこでCLRは、引き続き今ロードしたutil.dllに入っているマニフェストを探索して、Util型の情報を探す。ildasm.exeを使って探してみると、次のようなエントリが見つかるはずだ。
util.dllのマニフェストにある、Util型についての情報 |
Utilという型がこのアセンブリに定義されていることが発見できた。ただし、これではまだ型に関する詳細な情報が分からない。上記のエントリをよく見ると、“Implementation token”という属性がある。CLRはこれを見て、この型の実装は0x26000001というトークンを持つエントリから見つけられることを理解する。実際に引き続き同じマニフェストを探索すると次の定義が発見できる。
util.dllのマニフェストにある、アセンブリ内の別ファイルに関する情報 |
これはFileDefと呼ばれるエントリで、アセンブリが複数のモジュールで構成されているときに、アセンブリを構成しているファイル1つ1つを指し示しているエントリである。つまり、上記のExportedTypeエントリと組み合わせると、Util型は確かにこのアセンブリにあるものの、その実装はutil.netmoduleという、今ロードしたマニフェストが入っているファイル(util.dll)とは別のファイルに含まれていることが分かる。
− アセンブリのロード 第5段階 −
型を実装しているモジュールのロード
CLRは最後にutil.netmoduleをロードして、そのメタデータを開く。ここで注意してほしいのは、マルチ・モジュール・アセンブリの場合、そのアセンブリを構成するすべてのモジュールが同じフォルダに配置されていなければならないことだ。GACにインストールしたときは自動的にそうなるが、コードベースを利用しているときは慎重にファイルを配置しなければならない。この場合も、util.netmoduleがutil.dllと同じディレクトリになければ、“System.IO.FileNotFoundException”が発生して、コードの実行はここで中断してしまう。
util.dllと同様にildasm.exeでutil.netmoduleを開き、メタデータを表示して0x02000002というトークンを検索する。このトークンは上記のExportedTypeエントリに書いてある“TypeDef token”の値だ。すると、次の定義が見つかるだろう。
util.netmoduleのメタデータにある、Util型の詳細情報 |
これでUtil型の詳細情報が得られたので、CLRはメモリ上にUtil型に必要なメモリを確保し、CLRがコードを実行するために必要なさまざまなデータ構造をセットアップして、ようやく“Util u = new Util()”という1行のコードの実行が完了したことになるのだ。
マルチ・モジュール・アセンブリのメリットと注意点
ここまで長々と1行のコードを実行する手順を解説してきたが、この議論の中に、Visual Studio .NETでは作れないマルチ・モジュール・アセンブリをわざわざ作るメリット(「第1回 Managed Code/アセンブリ/モジュール」を参照)の1つと、注意すべき点がそれぞれ浮き彫りになったことに気付いただろうか。
上記の説明の第2段階から第4段階では、要約するとCLRは次のような動作をしていた。
- アセンブリ名からutil.dllを特定する
- util.dllをロードして、マニフェストを参照する
- util.netmoduleをロードして、型の情報を取得する
もしもutilアセンブリがutil.dllからなるシングル・モジュールのアセンブリだったら、3の手順は不要だったはずだ。もっと厳密にいうと、たとえマルチ・モジュール・アセンブリであったとしても、Util型の情報がたまたまutil.dllに含まれていれば、3の手順は不要だったはずだ。つまり、マルチ・モジュール・アセンブリでは、必要な型が、マニフェストが含まれるプライマリ・モジュールとは別のモジュールに実装されていた場合、ファイルをロードする手間が増えるということになる。ファイルのロードをできるだけ1度で済ますためには、マニフェストが含まれるプライマリ・モジュールに型の情報も含めるべきだということになる。これが注意点だ。
だが、この注意点はうまく使えばメリットにもなる。コンポーネントを開発していると、アプリケーションから非常に頻繁に使われる型と、よほどのことがない限り使われない型とに明確に分けられることがある。例えば、コンポーネントで独自に定義した例外は、その例外条件がめったに発生しないのであれば、あまり利用されないだろう。こんなときに、Visual Studio .NETを使ってシングル・モジュール・アセンブリを作ってしまうと、めったに使われないコードやメタデータが大量にメモリ上にロードされて、一度も参照されないまま破棄されることになる。これはまったく好ましくない。
そこで、あまり使われない型だけを集めて別のモジュールにまとめ、頻繁に使われる型はプライマリ・モジュールに入れれば、ロードの手間を省きつつ、メモリの使用効率を上げることができるのだ。これがマルチ・モジュール・アセンブリのメリットだ。
INDEX | ||
解説 インサイド .NET Framework | ||
第5回 アセンブリのロード(後編) | ||
1.アセンブリのロード 3〜5段階 | ||
2.アセンブリの配置に関する補足とまとめ | ||
「解説:インサイド .NET Framework 」 |
- 第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用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|