.NETのアセンブリ、とかの話
このページの趣旨
→ 自分で調べたり確認したりした事のメモ です。あと、
知っている事についての 自分の理解 とか を書いてみる。
以前に仕事とかで触れる機会があって、勉強したことがあるので
せっかくなのでメモっておこうと思った
---
(用語)
アセンブリ とは?
... アプリケーションなどの管理単位となるコンパイル済みのコード群
アセンブリ と関連する 重要なキーワード としては、
「アセンブラ言語」から「機械語」へのコンパイル(プログラムのコードを変換) とかです
.NETの アセンブリ とは? 具体的には何のことなのか
→ ソースプロジェクトを「ビルド」した際に、 ビルド成果物 として出力される ファイルなど の事
.exe ファイル .dll とかの事
ビルドの成果物 として出力されたファイル とかの「全部」を指す
つまり、VisualStudioとか でソースプロジェクトを「ビルド」したときに
bin\Release\フォルダに生成されるファイル 全部 を指す?
「アセンブリ」という意味では
ソース のプロジェクト(.csproj とか .vbproj ファイル)を「ビルド」した成果物として出力されるファイル (ビルド成果物)
とかの コンポーネント の総称の 呼び名のことです。
(まとめ)
アセンブリ とは つまり、
VB.Net や C# などのプログラミング言語で書かれたソースをコンパイル すると、
「コンパイル結果」として出力されるファイル とかの事の総称。
実行可能ファイル( .exe) または、
ダイナミック リンク ライブラリ ( .dll) ファイル
などを総称して、.NETアセンブリ(assembly)と呼びます
→(参考):.NET のアセンブリ - Microsoft Learn
---
.NETのアセンブリ のファイルには、一体どんな情報が格納されているのか? という話。
..
(用語)
高級言語 とか 機械語 のコードとは?
高級言語 と呼ばれるような言語で書かれたコードとは、
C#とかVB.Netとか で書かれた プログラムのコード です
高級言語で書かれたコード
それらが「プログラム」として実行されるとき、
ソースファイルに書かれたコードそのものが、直接そのままの形で
実行環境のシステム上で実行されるわけではない。です
実際に実行環境のシステム上で実行される瞬間(とき)、
.NETでは、内部的には、
ソースファイルに書いた 高級言語 のコードは、そのままの形で実行されるのではなくて、
👇次のような事が行われています。
⓪:(プロジェクトの「ビルド」時)
高級言語で書かれたコードのプログラムの内容を、「各言語ごとのコンパイラ」が「IL (後述)」に「コンパイル」する。
→ ソースファイルに書かれた「コード(高級言語)」が「ILコード(中間言語)」 にコンパイルされる。
.exe とか .dll とかの「.NETアセンブリ」が生成される。
①:(ビルドされたアプリケーションが「実行(起動)」される時)
IL で書かれたコードを、CPUとか が直接解釈できる形式のコードに「コンパイル」する。→(人間が読めない [機械語] みたいな コードになる。)
そして、そのコンパイル した機械語のコード を
CPUとか に渡してCPU上で実行してもらう。`
②:
→【結果】:プログラムとしてソースコードに書いた処理内容が、実行環境のシステム上で「プロセス」として起動されたりして、プログラミングされた「処理の内容」の実際の操作が、実行環境のシステム上で、実際に実行される。
みたいな感じのことが 行われています。
(Windows で例えると、)イメージとしては まさにタスクマネージャーとかに表示される プロセス みたいな 感じで
実行環境のシステム上で、他のシステム独立した単位で実行される「アプリケーション」として起動して、プログラムの処理が実行されていく みたいなイメージです
(結論)
つまり、.NETにおいて コンパイル のタイミングは2回ある。
① ビルド時:高級言語 → IL
② アプリケーションの実行(起動)時: IL → 機械語
--
→ 関連して、
IL 「(Intermediate Language→ 中間言語)」 の話
IL ーあいえる とは?
中間言語 intermediate language (IL) とは?
→ .NETにおいてIL とは、
ソースファイルに C#とかのプログラミング言語で書かれたコード:
「高級言語」で書かれたコード
と、
そのコードが実行環境のコンピュータ上で、実際に処理命令 として実行されるときのコード:
「機械語」のコード
この「中間」で使われる言語 のこと です。
--
intermediate ... 中間、中間体、インターミディエイト。
中間言語(IL) は何の「中間」なのか?
→ .NET において、↑上記の「高級言語」と「機械語」の 中間 で使われる言語、ということです
VB.NET や C#をはじめとする、.NET対応言語は、
各言語のそれぞれのコンパイラによって
.NETの共通言語ランタイム(CLR)が直接解釈できる「IL」という言語にコンパイルされます。
アプリやライブラリの ソースコード は、この IL の状態で配布されるのです。
↓
アセンブリ(.exe とか .dll)の中には、メタデータ(型情報 や 属性)と、ILコードが入っています。
メタデータ
例えば どのクラスがどういうメンバーを持っていて..とか、プログラムのバージョン情報 とか。
そういう情報が全部アセンブリ内に残る。
ILコード
どんな処理をするかの「命令」を記述したもの。
(参考ページ)>| IL の概要 - IL(.NET の中間言語) | ++C++; // 未確認飛行 C
C# やVB.Net などの .NET 上で動作するプログラミング言語は
ビルドすると、いわゆる 機械語、マシンコード にコンパイルされるのではなく、
一度、.NET の中間言語(intermediate language →IL)にコンパイルされます。
.NET の場合、中間言語のことを、
単に略して 「IL あいえる」 と呼んだり、
MSIL(Microsoft Intermediate Language)や CIL(Common intermediate language)と呼んだりします。
(以後、IL という表記に統一します。)
---
.NET上で、
プロジェクトを「ビルド」した際に行われること。 とか
また、~ そのビルドした アセンブリ(.exeファイル とか) が実行環境の上で実行されて、プログラムが実際に実行される までに行われること、
について
「ビルド」とは?
内部的には、(↓)以下のような事が行われている プロセス です
「ビルド」では、ソースファイルに書かれた「高級言語」のコードを、(各言語ごとの)「コンパイラ」がコンパイルして、
いわゆる 「IL(中間言語)で表現したコード」に変換する。
ソースファイルに書かれているコードは、
内部的には(.NETの共通言語ランタイム上では、)
ソースのプログラム言語が何であったとしても、
全て共通の言語 、「IL」コードとして変換されたコード としてロードされて、
実行される、
という仕組みになっている。
---
...
配布した「そのアプリの アセンブリ」が、
実行環境上でそのアプリケーションとして 実行(起動)しようとする際には、
その実行環境にインストールされている .NETの共通言語ランタイム によって、そのアプリの アセンブリファイル (.dll ファイルとか)の中に格納された ILコード がロードされて、
→ CPUが直接解釈して実行可能な「機械語」のコードとして コンパイル される。
その機械語のコードがCPUに渡されて、最終的にプログラムが実行される。
みたいなことが行われている。ということです
↓
つまり、
.NETのアセンブリ 上では、
ソースコードは(それが書かれたプログラミング言語が何であれ、)ぜんぶ同じ言語 - IL(中間言語) で書かれたコード として扱われる。
アセンブリ 内には、ILに変換されたプログラムコードが格納されており、それらが アセンブリ として配布される。
という仕組みになっている。
そして
→ そのプログラム(ビルドした アセンブリを)実行しようとするときには、
その 実行環境にインストールされている「.NETの共通言語ランタイム」によって、
アセンブリ内の ILのコード がコンパイルされて、「プログラムを実行するための 機械語 」に変換される、
その機械語のコードがCPUとかに渡されて、プログラム処理 の命令が実際に実行される。
→【結果】アプリが動作
というような仕組みで、プログラムのコードが実行されている のです
---
(以下、その他 雑なメモ)
---
ランタイム
(いわゆる「ランタイム」とか呼ばれるようなPG とは?)
その .NETのアプリケーションが、実際に
実行環境のシステム上で起動するときに行われるような事としては、
いわゆる「ランタイム」とか呼ばれるようなPGが仕事をする。
ランタイムが IL をコンパイルして「機械語」にする。
ランタイム とは、
「○○のソフトをPCにインストールして実行する」ために、
一緒にインストールが必要になるプログラム みたいなやつ のことです。
そのフレームワークのアプリケーションを動作させるために
前提として実行環境に一緒にインストールが必要になるプログラムのこと。
アプリを実行しようとすると、実行環境上 にインストールされた
その「ランタイム のPG」によって、以下が行われます。
① その.NETアプリケーションのアセンブリ がロードされる。
→ ②アセンブリ のファイルに格納されている情報 の内容から、「ILコード」が取得されます。
それで、
共通言語ランタイム は、その ILコード を、
CPUとかが直接解釈して実行できる形のコード、機械語にコンパイルする。
その後、 ランタイム のPGは、
→ 機械語 にコンパイルしたコードをCPU とか に渡して、プログラムとして実行してもらう..
という感じのことが行われています
つまり、 IL とは
→ソースファイルに書かれた「プログラミング言語 」と 「機械語」の中間 のコード形式として使用される言語 のこと です。
ソースコードの「ビルド」後 ~から、
~実行環境上で「実行」するために機械語に「コンパイル」されるまでに使われる、
アプリケーションの「ソースコード」の形式 の言語名
そこでのプロセスでの間で扱われる「コード」としての、プログラムの言語、それが IL と呼ばれる言語です
ということです。
---
-
| "マネージドコード" とは -Microsoft Learn
マネージド コードは、C#、Visual Basic、F# など、
.NET 上で実行できる高度な言語のいずれかで記述されたコード のことです。
これらの言語で記述されたコードをそれぞれのコンパイラでコンパイルすると、
共通言語ランタイムがコンパイルして実行するための「中間言語コード(ILコード)」が出力されます。
..
→> 簡単に言うと、マネージド コードとは、[共通言語ランタイム]によって実行が管理(マネージド)されるコードの呼称です。
C++ は、Windows 上で直接実行されるネイティブのアンマネージド バイナリも生成できるため、この規則の 1 つの例外です。
(共通言語ランタイム は CLR とも呼ばれます。)
IL を生成したら、それを実行します
ここで、CLR(共通言語ランタイム) が引き継ぎ、
コードを IL から、"CPU で実際に実行できるマシン コード" に
Just-In-Time コンパイル、つまり JIT 処理するプロセスを開始するのです。
IL は、ランタイム上で動作する特定の言語とは独立していることに注意してください。興味がある場合は、それについての別の仕様書を読むこともできます。-- "マネージドコード" とは -Microsoft Learn
↓
実際にコンパイルされた、Userクラス の IL のコードの例 を見てみましょう。
例えば、以下のようなクラス(Userクラス)があるとします。
code:(例:)UserClass.cs
//ユーザー情報を表すクラス
public class User
{
//【プロパティ】
/// ユーザID
public string UserID { get; set; }
/// 年齢
public int Age { get; set; }
//【メソッド】
///ユーザが成人かどうかを判定します。
/// <returns>True:成人である False:未成年</returns>
public bool IsAdult()
{
if (this.Age >= 18)
{
// 成人である
return true;
}
return false;
}
}
👆上の Userクラス をビルドする。
ビルドして生成された .DLL ファイルから、内の ILコードを採取して確認してみる。
ILコードの採取方法
→ILSpyというツールを使用しました
.NET ( -.NetFramework)用アセンブリの、逆コンパイル用のツール
ILSpy 9.0 · icsharpcode/ILSpy · GitHub
(ツールのファイルリンク)>ILSpy_selfcontained_9.0.0.7889-x64.zip
デコンパイラー.. .dllファイルを解析してバイナリ内のソースを 逆コンパイル して、C#などのコードとして表示できる。
↓
ILコードを採取してみた結果:
(↓)User クラスの定義部分 のコード
code:Userクラス定義 のILコード
.class public auto ansi beforefieldinit ConsoleApp1.User
extends System.RuntimeSystem.Object
{
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
01 00 00 00 00
)
// Fields
.field private string '<UserID>k__BackingField'
.custom instance void System.RuntimeSystem.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void System.RuntimeSystem.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype System.RuntimeSystem.Diagnostics.DebuggerBrowsableState) = (
01 00 00 00 00 00 00 00
)
.field private string '<UserName>k__BackingField'
.custom instance void System.RuntimeSystem.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void System.RuntimeSystem.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype System.RuntimeSystem.Diagnostics.DebuggerBrowsableState) = (
01 00 00 00 00 00 00 00
)
.field private int32 '<Age>k__BackingField'
●...(中略)
// Properties
.property instance string UserID()
{
.get instance string ConsoleApp1.User::get_UserID()
.set instance void ConsoleApp1.User::set_UserID(string)
}
.property instance int32 Age()
{
.get instance int32 ConsoleApp1.User::get_Age()
.set instance void ConsoleApp1.User::set_Age(int32)
}
} // end of class ConsoleApp1.User
IsAdult()メソッドの処理部分 のコード
code:IsAdult() のILコード
.method public hidebysig
instance bool IsAdult () cil managed
{
// Method begins at RVA 0x2430
// Header size: 12
// Code size: 29 (0x1d)
.maxstack 2
.locals init (
0 bool,
1 bool
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call instance int32 ConsoleApp1.User::get_Age()
IL_0007: ldc.i4.s 18
IL_0009: clt
IL_000b: ldc.i4.0
IL_000c: ceq
IL_000e: stloc.0
IL_000f: ldloc.0
IL_0010: brfalse.s IL_0017
IL_0012: nop
IL_0013: ldc.i4.1
IL_0014: stloc.1
IL_0015: br.s IL_001b
IL_0017: ldc.i4.0
IL_0018: stloc.1
IL_0019: br.s IL_001b
IL_001b: ldloc.1
IL_001c: ret
} // end of method User::IsAdult
(↑上に貼った 未確認飛行 C さんのページからの引用)
.(ピリオド) から始まる行がメタデータ定義。
..IL_0000 とかはラベル。制御フローに使う。 ldarg.0 とかが IL 命令。ほとんどが1バイト命令。 ごく一部、2バイト命令、4バイト命令もあり。
(まとめ)
→ 確かに、アセンブリには メタデータ(型情報 や 属性)や 処理(命令)が全て書いてあるっぽい ですね。
(実際よく分からないが
C#コード上では書いていない(省略している)ような定義も、ILではちゃんと 全部 書かれている っぽい
コンストラクタ →::.ctor()? とか
自動実装プロパティで省略してるPrivateフィールドのメンバ ?
.field private string '<UserID>k__BackingField'
とかの宣言っぽい部分 があるな、と思いました。(感想)
(ILの話 おわり)
---
..
---
?
ビルドの 「プラットフォーム」の設定、「anyCPU」と「x64」とかって? 一体何がちがう のか
例)「リリースする資源を「64bit向けの資源」としてビルドしたい ときに、例えばそのプロジェクトが「32bit向けの .dll」を参照していたとして、その状態でビルドしても、いいのか?ダメなのか?とか よくわからないね」みたいな話 とか
そしてその時に、
ビルドの設定は「x64」を指定しなきゃいけないのか?「anyCPU」でもいいのか?とか
「どっちでもいい」のか、どっちかにしなきゃダメ なのか?とか、
よく分からないね。(というかその辺のこと よく分かってない)みたいなはなし ありますよね
こういう 超細かい話 みたいなのは、
過去にも何回か、同じことを調べたことある 気がする
超細かい話すぎて何回調べても 覚えられない
超細かい話すぎて というか、理解するのが単純に難しい
覚えるのが難しいポイント:
アセンブリをビルドするときの「ビルド時の設定」と、
実行環境の「OSの bit数? .NETランタイムのバージョンとか?」の組み合わせ?とか
アセンブリの種類(ソースプロジェクトを「ビルド」するときの、ビルドの設定 とか
「実行環境」の 環境の条件 とか
そういう条件の組み合わせによって、コード実行時 の動作が変わる?とか
(実行環境が ○○ の場合は、 △△.. というコンポーネントが実行環境にインストールされていないと、アプリを実行(起動)できなくなる、とかの要件があったりして、
その辺の情報を探したり見つけるのに 苦労しがち
アプリの実行を、その環境で実行できる / できない)、とかが変わったり する?
のか?
とか
なんかいろいろ話が出てきて、
調べても、それ終わったあとマジですぐ 忘れちゃうよね (あるある)。という話
例えば 具体的にあったりする例としては、、
アプリの リリース資源 を作るときに、
リリースした資源(アセンブリ)が、そのシステムの納品先(インストール先の「実際に実行される環境」)でちゃんと確実に実行できるか?を事前に検証して確かめておきたい。とする
(できるだけ確実に実行できる資源にするには)ビルド設定は、どの組み合わせに設定するのが 正解か?みたいな事を調べたい、みたいなことがあった。
とりあえずその都度、調べたり 検証したり して
理解できていた つもり。でしたが、
(とりあえず、その時どきでは、)解消できてたはず
でも、結局ずっとよく分かってない
--
Just-In-Time コンパイルとは 何?
IL → ?(何か) へのコンパイル
コードを [IL] から、"CPU で実際に実行できるマシン コード" に
Just-In-Time コンパイル、つまり JIT 処理するプロセスを開始するのです。
つまりJIT処理するプロセス とは、.NETの 共通言語ランタイム の方 がやってる「コンパイル」のこと?なんだろうか