Journey of a .NET Project
In this article, I’ll be talking about the compilation steps (a.k.a journey) of a .NET Project. I first need to talk about Common Language Runtime (CLR) terms to make understanding easy of that journey.
Common Language Runtime (CLR)
.NET supports many programming languages such as C#, F#, Visual Basic, C++/CLI and more in the ecosystem. Every .NET project works in this runtime after the compilation progress to make it possible the work with diverse languages. There are also many good benefits such as Performance Improvements, Exception Handling, Easily referencing the components developed in other .NET languages, Garbage Collection, and more. You don’t need to be worried about garbage collection or other things when working with .NET via that fancy thing. Thanks to CLR, you can easily reference a visual basic project’s API into a C# project.
The first step of the Journey: Language Specific Compilation
Let’s say, you have a C# project; In this case, you will use the C# compiler which targets the .NET Runtime. This language compiler verifies the syntax and infrastructure of source code to make sure this code is usable by components written in other .NET languages. After being sure your syntax and the infrastructure are fine, the Microsoft Intermediate Language (MSIL) compilation progress starts.
Before to run: Managed and Unmanaged Codes
There are terms in the programming called “Managed and Unmanaged”, Managed codes get executed by a runtime via an intermediate language while unmanaged codes get executed by OS directly. The main reason for that difference is the type of codes; Unmanaged codes get compiled into direct Machine Code (which can be run by OS) while managed codes get compiled into an intermediate language (which is like MSIL and needs its runtime to run). In our case, For instance; while C++ is an example of unmanaged code, C# can be an example of a managed code type.
Microsoft Intermediate Language (MSIL) a.k.a IL Codes / OpCodes
As far as we learned in the previous step, our C# project is a managed code. When compiling to managed code, we first need to convert our C# language to an intermediate language: the MSIL. MSIL codes are CPU-independent sets of instructions that can be efficiently converted to native code. Here is an example of an IL body.
In the photo, you can see a set of instructions such as “nop, call, ret” and their meanings on the right side of the instructions. When a language-specific compiler creates MSIL codes, it also creates metadata for your project. That metadata contains the signature of each type’s members and these kind of things.
- Portable Executable (PE) file
The MSIL codes and metadata are contained in the portable executable (PE) file. This file type is based on a common object file format (COFF) and helps your OS to recognize common language runtime (CLR) images. On the other hand, your OS will know your code is something runnable with that thing. PE files have their structure which includes Headers, and Section Tables. While headers contain file type and other things, section tables contain our MSIL codes and that kind of thing.
Compiling MSIL to Native Code
You need to convert your MSIL codes to Native codes to make them runnable. There are two options for this in .NET;
- Just-in-Time (JIT) Compiling
- Ahead-of-Time (AOT) Compiling
Just-in-Time (JIT) Compiling
The main philosophy of JIT compiling is compiling the MSIL codes to native code dynamically while the application is running. The main benefit of that is you only compile what you need (reduces memory usage). The main disadvantages are initial startup time since it’s compiling things dynamically and security. When you use JIT compiling, anyone can see all IL bodies with a decompiler (such as ILSpy, dnSpy) since they are not pre-compiled and waiting there to be compiled in the runtime.
- Compilation Cycle
When you use JIT compiling, it just compiles the called method bodies just like I said, and never do business with them again since it’s already compiled. The compilation starts when you hit the run button for your application, firstly compiles the entry method of the application and follows the method calling cycle to keep running your application.
Ahead-of-Time (AOT) Compiling
When you choose this way to compile your MSIL codes, it pre-compiles everything instead of compiling dynamically. The main benefit is increasing the startup speed of the application and security, it also makes you able to zip reference DLLs into your main application (self-contained). You are also compiling everything directly to native code. When you use that way to compile your application, people cannot see the IL codes with a decompiler since they are compiled into native code.
Finally!
Well, there is one more step called Code Verification but I don’t prefer talking about it in this writing. After all of these steps, our .NET application is ready to run as a portable exe file. Thanks for reading!