.NET Frameworkがアセンブリを検索する順番と確認するためのツール


# 調査のきっかけ
.NET Frameworkを使用したプログラムの場合、初回起動が遅いことがあるため、
調査手法の１つとしてNGENを使用してGACに登録したことがありました。
（JITコンパイルも調査対象として考えていた）

その際に、GACから読み込めたのかを検証するために調査しました。
（結果的に速度はあまり変わらず、今回のケースはJITコンパイルがボトルネックではないという結論をくだしました）

あとは、アセンブリ参照時に指定されたバージョンと異なっていて、
dllはあるけど読み込めないというエラーが発生したことがありました。
その際も似たような調査をしました。
（リフレクションを使用したときにエラーがでており、詳細はinner exceptionを見ないと分からない状況でした）

# 参考
[ランタイムがアセンブリを検索する方法](https://docs.microsoft.com/ja-jp/dotnet/framework/deployment/how-the-runtime-locates-assemblies)
[Fuslogvw.exe (アセンブリ バインディング ログ ビューアー)](https://docs.microsoft.com/ja-jp/dotnet/framework/tools/fuslogvw-exe-assembly-binding-log-viewer)

# ランタイムがアセンブリを検索する方法
参考サイトをみていただくのが一番良いかと思いますが、
簡単に説明すると以下の順番になります。

1. 前に参照したアセンブリの検索
2. グローバル アセンブリ キャッシュ（GAC）の検索
3. コードベースによるアセンブリの検索
4. プローブによるアセンブリの検索

　

軽く補足します。（自分の理解度も低いため誤っていたらすみません）

## 前に参照したアセンブリの検索
文字通りです。
既に読み込んでいる場合はそれを参照します。


## グローバル アセンブリ キャッシュ（GAC）のチェック
.NET FrameworkはJITコンパイルという方式を採用しているため、
各アセンブリは実行時に中間コードからコンパイルされます。

[実行時コンパイラ](https://ja.wikipedia.org/wiki/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9)

JITコンパイルの短所は中間コードからのコンパイル時間がオーバーヘッドとなることです。

この短所を補うために、.NET Framework本体（ランタイム）等のように修正頻度が低いアセンブリについては
事前コンパイル（AOT）されています。

この事前コンパイルされたアセンブリが格納されている場所？（アセンブリ群？）を
グローバルアセンブリキャッシュ（GAC）といいます。
（GAC=事前コンパイルではないかもしれませんが、自分はこう捉えています）

以下に格納されています。
%windir%\Microsoft.NET\Assembly



## コードベースによるアセンブリの検索

アセンブリと同名のconfig（hoge.exe.config）を作成することで
様々な設定ができます。

その１つに[codeBase要素](https://docs.microsoft.com/ja-jp/dotnet/framework/configure-apps/file-schema/runtime/codebase-element)があります。

codeBase要素を記載することでdllの参照先を指定できます。


## プローブによるアセンブリの検索

プローブは上記の参考サイトで以下のように説明されています。
> プローブとは、アセンブリの名前およびカルチャに基づいてアセンブリを特定するための一連のヒューリスティックです。


ヒューリスティックとは、必ず正しい答えを導けるわけではないが、ある程度のレベルで正解に近い解を得ることができる方法とのことです。

コードベースも記載せず、.NET Frameworkのランタイム以外のdllを参照する場合、
基本はGACにいないため、以下の順に探します。

* アプリケーションが実行されるルート位置
* 参照先アセンブリのカルチャ属性
* 参照先アセンブリの名前
* probing要素の privatePath 属性（これは調査不足のため割愛します）

 　


ちなみにですが、プローブによる検索は遅いと言われることがあるようです。

私の場合は一番先頭の「アプリケーションが実行されるルート位置」で大体が片付くので
体感することは今のところありません。


# ツールを使った確認方法

※VisualStudioのインストールが必要です。

VisualStudioをインストールすると、
Fuslogvw.exe (アセンブリ バインディング ログ ビューアー)が付属されています。

このツールを使うことで、上記の流れを確認することができます。

## ツールの起動

 開発者コマンドを管理者権限で起動します。
「Fuslogvw」と入力し、実行します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124594/cda83e45-f954-a0a2-1402-e93a911d0fba.png)


## ログの監視を開始する
ツールが起動するので設定ボタンをクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124594/ed008b70-8499-eb1c-5538-12a47ce4e1d0.png)

初期は「ログを無効にする」なので、「すべてのバインドをディスクに記録する」を選びます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124594/6c94c21c-e792-a78d-71cf-f228e6d322f0.png)

なお、管理者権限で実行していない場合は設定変更ができません。


これで準備完了です。


## 試してみる
例えば以下のようなdllを事前に作成します。

```c#:myApp.dll
using System;

namespace myApp
{
    public static class Class1
    {
        public static string Get()
        {
            return "test";
        }
    }
}
```
　
このdllをConsoleApp.exeで参照して呼び出します。

```c#:ConsoleApp.exe
using myApp;
using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Class1.Get());
            Console.ReadLine();
        }
    }
}

```

　

参照先myApp.dllを削除してConsoleApp.exeを起動すると以下のようにログが表示されます。
ConsoleApp.exeは「C:\temp\ConsoleApp\bin\Release\app.publish」配下にあります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124594/9f7b8424-c0c4-c1a5-f749-48e0ae1d81aa.png)
　
2行目の説明欄がmyAppから始まるログをダブルクリックすると
アセンブリがどのようなルートで検索されたのかが分かるようになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124594/9670eaa5-8692-d520-31ac-a5349fd9f429.png)


GACを参照したという形跡が見えませんが、
.NET Frameworkのランタイムを読み込んだ場合は以下のようなログがでます。
> ログ: GAC でアセンブリが見つかりました。


確認後はログを無効にして終了です。

# 最後に
正直、理解できていないことのほうが多いです。
codeBase指定の場合は[厳密な名前付きアセンブリ](https://docs.microsoft.com/ja-jp/dotnet/framework/app-domains/strong-named-assemblies)である必要があったかどうかも記憶があいまいだったりします。

ただ、JITコンパイル、GACへの登録方法、exe.configの設定種類等、
これきっかけで横道にそれながら調べごとをすることも増えたのでいい経験にはなりました。