The intention behind writing this article is to share one of the best practices to be followed before starting with any new technology, i.e., to know the basics behind the technology. Also, a reminder to all .NET developers to look back and see what they are missing. The intended audience of this article are the ones who are interested in knowing boot strapping process of .NET Application. Of course, developers are very smart and will be aware of these concepts. If you are already aware, it will be a point of reference or else, it will be added to your knowledge database.
Introduction
We always start learning any new technology with our buddy “Hello World
” program :). If program executes, then we conclude that all pre requisites which consist of any configuration with respect to technology are all set properly and we are set to dive in. With this, we also understand the basic life cycle of a program. All developers are desirous of executing program and observe the output but are least bothered what happens internally. Of course, you can develop systems without knowing its internals as well but it’s always incomplete. Developer can always provide a fool proof solution only when he is aware of its complete life cycle. Let’s start our journey.
Background
Bootstrapping usually refers to the process of loading the basic infrastructure into the memory of a computer which will then take care of loading other software as needed. The process involves a chain of stages, in which at each stage a smaller simpler program loads and then executes the larger more complicated program of the next stage. When these statements are compared with our .NET application, bootstrapping includes loading of basic infrastructure (CLR) into process in which CLR further creates AppDomain
(Default AppDomain
) and loads all required as well as referenced assemblies to AppDomain
. Let’s brush up what is CLR, AppDomain
and assemblies. We will not go in depth of these statements since the intention of this article is only bootstrapping of these elements and for further information on these elements, you can refer to other articles or MSDN knowledge bank. Below mentioned are the three important entities responsible for running .NET application.
- CLR (Common Language Runtime): It is the one responsible for managing and executing the code contained in assemblies. The core features of the CLR (such as memory management, assembly loading, security, exception handling, and thread synchronization) are available to any and all programming languages that target it.
- AppDomain: An
AppDomain
is a unit of isolation and a logical container for a set of assemblies.The first AppDomain
created when the CLR is initialized is called the default AppDomain
; this AppDomain
is destroyed only when the Windows process terminates. - Assembly: It’s a basic unit of deployment and versioning. Assembly will have one or more managed modules.
Using the Code
The code snippet presented here just demonstrates getting hold of current AppDomain
, creating a new AppDomain
and loading assembly to newly created AppDomain
. The article further explains in depth about all these concepts and the one who is responsible for creating all these entities.
static void Main(string[] args)
{
AppDomain currentDomain = Thread.GetDomain();
Console.WriteLine("Current AppDomain name :" + currentDomain.FriendlyName);
Test testObj = new Test();
testObj.Display("Hi, Display method called from Current AppDomain");
AppDomain domain = AppDomain.CreateDomain("MyNewDomain");
Console.WriteLine("Name of the newly created domain: " + domain.FriendlyName);
Assembly assemblyObj = domain.Load("SampleTest");
Console.WriteLine("SampleTest assembly loaded in the new App Domain(MyNewAppDomain)");
Type myType = assemblyObj.GetType("SampleTest.Test");
var type = Activator.CreateInstance(myType);
myType.InvokeMember("Display", BindingFlags.InvokeMethod, null, type, new object[]
{ "Hi, Display method called from newly created AppDomain" });
Console.ReadKey();
}
Behind the Scenes
We all know the fact that any windows/ .NET application runs in its own process. This is the awesome facility given by Windows Platform to secure applications and its data. From the above figure, you can notice that application is under a single process. One process can consists of many AppDomains
. Each Process has its own copy of assemblies loaded into it. And also, you can notice that even though both the AppDomains
have System.dll, there is no provision for sharing the common assemblies except Domain Neutral assemblies such as MsCorlib.dll since the concept of AppDomain
is to provide complete isolation. Only those DLLs that are so integral to the .NET Framework get loaded as domain neutral fashion to maximize the resource utilization. Process performs all these functions with the help of Main
thread created. Process gets its life only through the Main
thread created, otherwise it's just lifeless. This is just an overall glance of the system where you can relate Process, CLR, AppDomains and assemblies.
Note: The main drawback of domain neutral assemblies is that it can never be unloaded until the application shuts down.
Summary
- .NET/Windows application runs in its own process.
- Each process can have more than one
AppDomain
. - No assembly can be shared between
AppDomain
s except Domain Neutral Assemblies. - No assembly can be unloaded from any
AppDomain
, the only way to unload assembly is to unload the complete AppDomain
.
Application Boot Strapping Process
Till now, you came across the actual snapshot of a .NET application with respect to process. Now let us continue how all the required components shown in the above figure get loaded into the process and how application gets its life. In .NET, all the executable applications such as Console UI Applications, Windows Forms Application, and WPF applications are all examples of self hosted applications and have managed EXE files. We know that any windows managed or unmanaged executable application should adhere to PE (Portable Executable) file format. Operating System doesn’t differentiate between .NET assemblies and Win32 executable binaries; for it, they are same normal PE files. But actual differentiation takes place only after reading the contents available in PE Header where Windows gets educated with many details like what kind of process to be created (32 OR 64), Whether assembly is managed or unmanaged?, type of file and all the required information. PE file format is actually in the form of sections.
It's a standard protocol that any Windows platform executable should adhere to. Each section behaves as a reference data for Windows Process to take necessary action (In short, it can be as a metadata). They all have, for default, three sections: .text, .reloc, .rsrc. The .text section contains the Import Table, the Import Address Table, and the .NET Section. The .reloc is just there to relocate the address which the Entry Point instruction jumps to (it's the only address contained in the IAT). The .rsrc section contains just the main icon for an executable, since all other resources are in the .NET Section. The .NET section consists of CLR header which provides all the information related to CLR such as version, location, size, entry point, metadata that makes assembly a managed module.
Using this data, Windows process can perform all required initializations and loading of data properly. Here, we don't walk through each section but we only consider sections required for .NET application. To make the discussion more interesting, we will draw a flowchart for the entire process which gives a clear picture.
Note: The process of boot strapping is still more complex, but I have taken out only the flavour of overall process and keeping in mind only the process related to .NET application. There exists many more intermediate stages which is not in the scope of this article.
The basic boot strapping process of any .NET executable
Summary of Steps Involved in Boot Strapping
- User double clicks .exe(Executable) file.
- Windows validates PE file. The standard Windows PE file header, which is similar to the Common Object File Format (COFF) 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. So based on this information, Windows creates an appropriate process to proceed further execution.
- Next, it examines the 15 directory of .text section which gives information whether the executable is managed or unmanaged.
- If it is managed executable, then Process loads MSCorEE.dll(Shim). It is also called as Microsoft Component Runtime Execution Engine.
- Process examines CLR Header to find version of CLR to be loaded and educates Shim with CLR information.
- Shim creates instance of CLR and CLR gets initialized. It is the heart of the .NET Framework since it is the one responsible for creating CLR instance.
- CLR creates Default
AppDomain
. Post this process, CLR is also responsible for initializing memory, thread synchronization and so on. - CLR loads all required as well as referenced DLLs to
AppDomain
. Note: Referenced DLLs are loaded on a need basis. It means all referenced DLLs are not loaded at once, it gets loaded only when data related to the respective DLL is accessed. Once loaded, it cannot be unloaded. - CLR invokes
Main
method and the application gets launched.
CLR Bootstrapping Process
If you still dig out the activities of CLR, we can find the following operations performed by CLR at the time of its Boot Strapping process. Before the CLR executes the first line of the managed code, it creates three application domains. Two of these are opaque from within the managed code and are not even visible to CLR hosts. They can only be created through the CLR bootstrapping process facilitated by the shim — mscoree.dll and mscorwks.dll.
System Domain: The SystemDomain
is responsible for creating and initializing the SharedDomain
and the default AppDomain
. It loads the system library mscorlib.dll into SharedDomain
. It keeps track of all the domains in the process and implements functionality for loading and unloading the AppDomain
s.
Shared Domain: All of the domain-neutral code is loaded into SharedDomain
. Mscorlib
, the system library, is needed by the user code in all the AppDomain
s. It is automatically loaded into SharedDomain
. SharedDomain
also manages an assembly map indexed by the base address, which acts as a lookup table for managing shared dependencies of assemblies being loaded into DefaultDomain
and of other AppDomain
s created in managed code. Fundamental types from the System
namespace like Object
, ValueType
, Array
, Enum
, String
, and Delegate
get preloaded into this domain during the CLR bootstrapping process.
Default Domain: DefaultDomain
is an instance of AppDomain
within which application code is typically executed. While some applications require additional AppDomain
s to be created at runtime, most applications create one domain during their lifetime. All code that executes in this domain is context-bound at the domain level. If an application has multiple AppDomain
s, any cross-domain access will occur through .NET Remoting proxies.
The basic boot strapping process of CLR
Some Facts about CLR
- Microsoft implemented CLR as COM server contained inside a DLL.
- The COM server representing CLR is registered in the Windows Registry just as any other COM server.
- The actual code of CLR is contained in a file called MSCorWks.dll for Versions: 1.0, 1.1, 2.0, and for version 4.0 it is contained in the file Clr.dll.
- To create CLR instance in a process, you can call
CLRCreateInstance
function defined in MSCorEE.dll which returns ICLRMetaHost interface
. A host can call GetRuntime
function of ICLRMetaHost interface
specifying the CLR version. This shim then loads the desired version of CLR into hosts process. In this way, you can even take care of loading CLR with required configurations. - The CLR can be disabled in an application which stops only managed code in execution but unmanaged code can still continue running.
- CLR header provides all the information related to CLR such as version, location, size, entry point, metadata that makes assembly a managed module.
Some Facts about AppDomain
- The first
AppDomain
created by CLR is called as Default AppDomain
. - The default
AppDomain
is destroyed only when the Windows Process Terminates. - Objects created in one
AppDomain
cannot be accessed directly by code in another AppDomain
. To do so, you can use marshalling (By Val or By Reference) for communication. AppDomain
can be unloaded. No assembly in AppDomain
can be unloaded. To do so, you need to unload the AppDomain
itself. - Each
AppDomain
created can be individually configured which includes configurations related to assembly loading, search paths, shadow copying and soon. - There is no hard-coded limit to the number of
AppDomain
s that can be running in a single Windows Process. - When CLR loading the assembly to destination
AppDomain
, it uses the configuration settings of that particular AppDomain
. FirstChanceException
of AppDomain
is the one responsible to catch any exception occurred in the AppDomain
if any object is listening to it.
Note: The bootstrapping process is very vast and complex. I have tailored it to give you a basic understanding of the process. There are many stages but have considered only the main stages responsible for launching a .NET application.
Points of Interest
It was really interesting writing this article. There is not even a single data store where I could get sequential information of bootstrapping process. There are many articles on this topic, but it was very difficult for me to map the actual flow. It was very interesting to know the concepts in depth while trying to collect this information. Hope you have enjoyed reading this article. Please do suggest if there is anything to be looked upon.