| |
| Ever wondered what happens behind
the scenes when we click a managed executable (.EXE). Yes, the
application starts, but how does it starts and what all happens
behind the scene. Here we will talk, what all happens when a
managed application is launched. But before that we’ll
go through a basic overview of CLR Hosting! |
| |
| CLR Hosting defines how the
CLR is loaded into the process, the transition from unmanaged
environment to managed environment and execution of applications.
There is a Runtime Host, an unmanaged code
shim, which loads the CLR into the process. This runtime host
first creates an Application Domain called
“Default Application Domain” where the runtime is
loaded. Then the control is transferred to this managed runtime
and it further creates more application domains, load managed
user code and executes them without using any unmanaged code.
This execution of user code into the managed environment without
any calls going to unmanaged environment gives us a performance
increase because making calls from managed to unmanaged is expensive.
Another feature of Hosting is that it provides some unmanaged
APIs also with which we can write our custom runtime host specifically
designed for our application. |
| |
| Now we will explain the process
which happens behind the scene before the application starts.
When a managed EXE is clicked, first the CLR is loaded into
the process. When the runtime is loaded, then the control is
passed from unmanaged code to manage code. This managed code
further creates application domain and loads the user code into
these application domains. CLR then checks the user code for
verification, type safety, security checks, etc. After it finishes,
the Class Loader finds the class (the entry point) inside the
assembly, loads it and loads other classes which are directly
called from the entry point method. JIT compiles this managed
code to native CPU instruction set and let the processor executes
these instruction sets. JIT does not compile all the managed
code into the native code at once. It does it on demand basis.
Means during the execution time of the user code, if the runtime
engine needs to use the code of another assembly or same assembly,
it converts into native code at that time, not during the first
JIT. Let see this process in detail. |
| |
| Loading the runtime
into the process |
| |
| Before any managed code is executed,
the runtime must be loaded into the process and initialize the
common language runtime. This loading of runtime into the process
is to be done by an unmanaged code because for executing a managed
code, we still need the runtime into the process and this is
what we want to do. So we have an unmanaged code stub from where
we start. This unmanaged code stub is in the “mscoree.dll”
file placed in “%windir%/system32”. .NET Framework
provides a set of unmanaged APIs called “Hosting APIs”
which helps us in loading the runtime. An unmanaged method,
CorBindToRuntimeEx, is called for hosting the
runtime into the process. This method have few parameters which
defines which version of runtime is to be loaded, which runtime
build to load (server or workstation), which garbage collection
technique to be used (concurrent or non-concurrent) and other
details. Also this unmanaged code stub creates a default application
domain where the runtime is to be loaded because in .NET Framework,
managed code is loaded into application domains (logical process
which restrict the application boundary) and executed there
hence required. |
| |
| Transition from unmanaged
code to managed code |
| |
| Once the default application
domain is created, there is a need to transfer the control from
the unmanaged code to managed code. There are few reasons for
that. Firstly, if we don’t transfer the control, then
the user code would be loaded in a managed environment and any
call originating from the user code need to cross the managed
environment to the unmanaged environment and this is expensive.
There will be a performance hit in this case. Moreover if the
user code is managed by runtime in the managed environment only,
it provide more security and easily manageable without any interoperability
layer coming in between. |
| |
| Once the default application
domain is created, the runtime host gets a pointer to that default
domain and loads the runtime into that default domain. To get
the pointer to the default domain, we have an interface called
ICorRuntimeHost which enables the host to accomplish
task like configuration setting, getting default domain, etc.
Once the host gets the pointer of the default domain it loads
the managed portion of runtime into the default application
domain. When the transition is complete, the unmanaged stub
is no longer required as our runtime manages everything from
this point. |
| |
| Runtime create App Domains
and loads the user code |
| |
| To load the user code and execute
it, there should be more application domains beside the “default
application domain”. In “Default application domain”
only hosts the runtime and domain-neutral assemblies can reside.
For user code, the runtime have to create more application domains
specific to the application. The runtime decides where the application
domain is to be created depending on the application memory
requirement and isolation factor. Again the runtime sets the
user application domain configuration settings like setting
the root directory from where the application domain will be
looking for private assemblies. Isolation is an important factor
because if the applications are not isolated properly, then
breakdown of one application may lead to breakdown of other
application sharing the application domain. When the application
domain is created and its configuration is set, the managed
user code (Assembly) is loaded in the application domain. Here
managed user code means any code that is not part of the host.
The user code is loaded into the application domain using System.Appdomain.Load
() method which is overloaded with various inputs or emitting
a dynamic assembly using System.Reflection.Emit namespace. |
| |
| Runtime verifies the
assembly loaded; checks for type safety, security |
| |
| Any user code which is loaded
into the application domain has to pass the verification test
done by runtime unless it is configured to be bypassed by the
administrator. In verification, the runtime checks the MSIL
and Metadata to find out whether the code can marked as type
safe which means that every object in the code refers to a known
and authorized memory location. It also checks if the referencing
object is strictly compatible with the object that is being
referenced to. These are the basic checks that are done by runtime
for type safety. Also security checks are done here. It checks
if the caller has the permission to execute this code. All security
checks are done for every method in the assembly. |
| |
| Class Loader loads the
classes from the assembly |
| |
| The loader reads the metadata
and code modules, identifies the “entry point” (method
that is executed when the code is first run). This incorporates
a complex process of loading a class from a physical file from
hard disk/network, from a dynamic assembly which is in the memory,
caching the Type information, performing some JIT and garbage
collection housekeeping, and then passing control to the JIT
compilers. This loader basically figures out the number and
type of fields so that runtime can create instances for the
same. It also has to check the classes it is loading, does it
have any relationship with other classes like is it a derived
class or a parent class because some of the member might be
overridden from the base class in child class. Then these classes
are forwarded to JIT to native compilation. The loader doesn’t
forward all the code to JIT. When the method is needed, only
then it is given to JIT for compilation. So the method which
are not called directly from the entry points, there stubs are
created and when these methods are called, these stubs passes
the control to JIT. |
| |
| JIT compiles the MSIL
to native managed code |
| |
| JIT a synonym
for “Just In Time” Compiler converts
the MSIL to native CPU instruction set. The instruction set
to which the MSIL is converted is specific to the CPU where
the code is being executed. This helps in using the extended
instruction set specific to that CPU Architecture and model.
JIT compilation thinks that some code would never be executed
so it just compiles the necessary code to start into native
code. Rest all the code is compiled on demand basis, means as
and when the method needs to be executed; the stub attached
to the method passes the control to JIT to compile the MSIL
code. Once the MSIL code is converted to native code, it need
not convert it again and again on subsequent calls. This native
code is cached and used for subsequent calls. |
| |
| Now the MSIL is converted into
native code, the application starts and we see our User Interface
for the application; may it be Web application or Windows based
Application. I have tried to give you an idea what all happens
behind the scene including the CLR internals as to how it works.
|
| |
| |
| |