DEV Community

Kristijan Crnac
Kristijan Crnac

Posted on

48 1 1 1

.NET compilation process explained (C#)

The CLR, you heard about it but you're not quite sure what it does or how it does certain things? JIT compiler seems familiar but where does it fit in the execution process? Keep reading and you can find out ๐Ÿ™‚

Overview of the execution process

  1. A developer writes C# code
  2. C# compiler checks the syntax and analyzes the source code
  3. Microsoft intermediate languages (MSIL) is generated as a result (EXE or DLL)
  4. CLR gets initialized inside of a process and runs entry point method (Main)
  5. MSIL gets converted to native code by the JIT compiler

Execution process

CLR

Common language runtime (CLR or just runtime) is an environment that runs the code and provides services that make the development process easier.

Sure, yet another definition that is not quite clear. Let's define it using an example:

You write code, compile it, and the next step in the process is the responsibility of CLR - it compiles MSIL (DLL or EXE) and creates an environment in which your code can be executed.

The runtime also provides the following benefits:

  • Memory management
  • Security boundaries
  • Type safety
  • Exception handling
  • Garbage collection
  • Performance improvements

You can develop your code in any programming language you desire as long as the compiler (e.g. C++/CLI, C#, Visual Basic, F#) you use to compile your code targets the runtime.
When working with .NET you'll often encounter the term "managed code" which is code whose execution is managed by a runtime. Runtime is in charge of taking the managed code, compiling it into native code, and then executing it.

More on runtime role in execution process in the following sections.

Compiling source code

When compiling to managed code, the compiler translates your source code into Microsoft intermediate language (MSIL), which is a CPU-independent set of instructions that can be efficiently converted to native code. Regardless of which compiler you use, the result is a managed module which is a standard 32-bit Windows portable executable (PE32) file, or a standard 64-bit Windows portable executable (PE32+) file that requires runtime to execute.

Compilation process

What is inside of managed module?

In addition to emitting MSIL, a compiler targeting the CLR is required to emit full metadata into every managed module. In brief, metadata is a set of data tables that describe what is defined in the module, such as types and their members, then what the managed module references, such as imported types and their members, etc.

Metadata is always embedded in the same EXE/DLL as the code (MSIL), making it impossible to separate the two.

Managed module

PE header - if the header uses the PE32 format, the file can run on a 32-bit or 64-bit version of Windows. If the header uses the PE32+ format, the file requires a 64-bit version of Windows to run. This header also indicates the type of file: GUI, CUI, or DLL, and contains a timestamp indicating when the file was built.

CLR header - includes the version of the CLR required, entry point method (Main method), location/size of the moduleโ€™s metadata, resources, strong name, flags, etc.

Metadata table - describes the types in your code, including the definition of each type, the signatures of each type's members, the members that your code references, and other data that the runtime uses at execution time.

Managed code (MSIL) - code the compiler produced as it compiled the source code

Loading CLR

When running an executable file, Windows examines this EXE file's header to determine whether to create a 32-bit or 64-bit process. Once created, Windows additionally checks the CPU architecture information embedded inside the header and accordingly loads MSCorEE.dll into the process's address space.

Depending on CPU type in the computer Windows loads x86, x64 or ARM version of MSCorEE.dll

Process's primary thread calls a method inside MSCorEE.dll which initializes CLR, loads the EXE assembly, and calls its entry point method (Main). At that point, the managed application is up and running.

JIT and execution

JIT (just-in-time) compilation converts MSIL to native code on demand at application run time when the contents of an assembly are loaded and executed. Because the MSIL is being compiled "just in time", this component of the CLR is frequently referred to as a JITter or a JIT compiler.

JIT compilation takes into account the possibility that some code might never be called during execution. Instead of using time and memory to convert all the MSIL in a PE file to native code, it converts the MSIL as needed during execution and stores the resulting native code in memory so that it is accessible for subsequent calls in the context of that process.

Execution example

.NET execution flow

Once the Main method of Program.exe is called, and WriteLine is set to be executed next, the JITCompiler function searches assembly's (Console.dll) metadata for the called method's (WriteLine) MSIL. JITCompiler next verifies and compiles the MSIL into native code and saves it in a dynamically allocated block of memory.

Then, JIT replaces the reference of the called method (WriteLine) with an address of the block in memory containing the native code it previously compiled.

Finally, the JIT jumps to the code (this code is the actual implementation of WriteLine(string) method) in the memory block, executes it and returns to Main from where it continues execution as normal.

The second time WriteLine is executed, code for the WriteLine method has already been verified and compiled, meaning the call goes directly to the block of memory where native code is stored.

Subsequent calls to the JIT-compiled method go directly to the native code.

Verification

While compiling MSIL into native code, the CLR performs a process called verification which ensures that everything the code does is safe. For example, verification checks that every method is called with the correct number of parameters, that each parameter passed is of the correct type, that every methodโ€™s return value is used properly, that every method has a return statement, etc.

Final words

I hope this summarized article helped you to at least get an overview of the CLR and .NET compilation process in general. If you have any constructive feedback please let me know in the comment section ๐Ÿ™‚

Below you can find additional resources if you want to dig deeper into terms that were mentioned but did not get enough attention in this article.

Credits and resources

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (6)

Collapse
 
pranayneema23 profile image
Pranay Neema โ€ข

Thanks for writing this. It is very helpful!!

Collapse
 
insight_it_891bf8cd689700 profile image
Insight IT โ€ข

Nice blog and informative content,
We are providing Best Dot NET Training in Hyderabad,
Thanks for sharing with us,
DOT NET Training in Hyderabad
DOT NET Online Training in Hyderabad

Collapse
 
dotnetian profile image
Matin Mohammadi โ€ข

Great explanation ๐Ÿ‘Œ

Collapse
 
__9745732b8a0f66b0 profile image

b.netูƒูŠู ูŠู…ูƒู†ู†ูŠ ุงู„ุชุนุฏูŠู„ ููŠ ุงู„ุตูุญู‡

Collapse
 
__9745732b8a0f66b0 profile image
ูŠูˆุณู ุนูˆุถ โ€ข

ูƒูŠู ูŠู…ูƒู†ู†ูŠ ุฏู…ุฌ ุงูƒูˆุงุฏ ููŠ ุงู„ุตูุญู‡

Collapse
 
balajee4dev profile image
balajee4dev โ€ข โ€ข Edited

thanks content was crisp to understand. is this same for .net core compilation ?

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed: Zero in on just the tests that failed in your previous run
  • 2:34 --only-changed: Test only the spec files you've modified in git
  • 4:27 --repeat-each: Run tests multiple times to catch flaky behavior before it reaches production
  • 5:15 --forbid-only: Prevent accidental test.only commits from breaking your CI pipeline
  • 5:51 --ui --headed --workers 1: Debug visually with browser windows and sequential test execution

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

Watch Full Video ๐Ÿ“น๏ธ

๐Ÿ‘‹ Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someoneโ€™s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay