Advertisment

CLR Internals

author-image
PCQ Bureau
New Update

The CLR (Common Language Runtime) is the platform on which applications written in multiple languages can run in a secure, efficient and robust manner. It provides the developers with a set of services so that they can focus on writing their code and leave the management of memory, metadata, and other common chores to the system.

Advertisment

We’ll now delve into the internals of the CLR. We’ll start with a simple program and refine it to show the various services offered by the runtime to the developer.

Figure 1 shows a program written in C# output a message to the console.

Figure 1:



*Allow easy reference to classes in the System namespace*


using System;


*This “class” exists only to house the application’s entry-point function*


class MainApp {


*Static method called “Main” is application’s entry point function*


public static void Main() { *Write text to the console Console.*


WriteLine(“The NET NET of DOTNET is that it ROCKS!!”); 


}


}







Advertisment

There are two interesting features about this program. First, the ‘using system’ line tells the runtime that we are using system services, in this case, writing to the console. Included with .Net will be a base-class library that contains several class definitions, where each class exposes some feature of the underlying platform. Since Microsoft defines literally hundreds of classes in the base library, the library is divided into namespaces that group related classes together.

For example, the System namespace (which you should become most familiar with) contains the base class, Object, which all other classes ultimately derive from. In addition, the System namespace contains classes for exception handling, garbage collection, console I/O, as well as a bunch of utility classes that convert safely between data types, format data types, generate random numbers, and perform various math

functions.

To access any of the platform’s features, you need to work with these namespaces and their defined classes. If you want to customize the behavior of any class, you can simply derive your own class from the desired base library class.

Advertisment

Secondly, when the C# compiler is used to compile this file it produces an executable which has intermediate language or MSIL stored in it. The same code as shown in Figure 1 is shown in intermediate language format in Figure 2.

Figure 2:



.method public hidebysig static void Main() cil managed


{


.entrypoint


// Code size 11 (0xb)


.maxstack 8


IL_0000: ldstr 


“The NET NET of DOTNET is that it ROCKS!!”


IL_0005: call void System.Console::WriteLine(string)


IL_000a: ret


} // end of method MainApp::Main








MSIL is much higher level than most CPU machine languages. It understands object types and has instructions that create and initialize objects, call virtual methods on objects, and manipulate array elements directly. It even has instructions that raise and catch exceptions for error handling. The important thing to note about MSIL is that it is not tied to any specific CPU platform. This means that an executable file containing MSIL can run on any CPU platform as long as the operating system running on that CPU platform hosts the .Net common language runtime engine. Initially, Microsoft will offer the runtime on the Windows platform.

Advertisment

Of course, the MSIL instructions cannot be executed directly by today’s host CPUs. So, the common language runtime engine must first compile the MSIL instructions into native CPU instructions.

When the common language runtime loads a class, it connects stub code to each method. When a method is called, the stub code directs program execution to the component of the common language runtime engine that is responsible for compiling the method’s MSIL into native code. Since the MSIL is being compiled just-in-time (JIT), this component of the runtime is frequently referred to as a JIT compiler or JITter. Once the JIT compiler has compiled the MSIL, the method’s stub is replaced with the address of the compiled code. Whenever this method is called in the future, the native code will just execute and the JIT compiler will not have to be involved in the process. As you can imagine, this offers significant performance benefits. This process is shown in Figure 3.

Figure 3:



Source --> Language Compiler --> MSIL --> JIT Compiler --> Native Code

Advertisment

Next, let us modify the program to do the same functionality but use a helper class to achieve it. Figure 4 shows the same program as Figure 1, but this time the class to output the message resides in an external module.

Figure 4:



*Allow easy reference to classes in the System namespace*


using System;


*Refer to the external class that will output to the console*


using OutputMessage;


*This “class” exists only to house the application’s entry-point function


class MainApp {


*Static method called “Main” is application’s entry point function*


public static void Main() {


*Create an instance of a class that will output to the console*


OutputMessage outMsg = new OutputMessage();


*Call the function that will output to the console*


outMsg->PrintMessage();


}


}












Figure 5 shows the “OutputMessage” class that is defined in an external module.

This module (a dll) is referenced when compiling the main application.

Advertisment

Figure 5:



*A helper class defined in a separate module from the main application*


class OutputMessage {


*This function outputs to the console*


public void PrintMessage() {


*Write text to the console*


Console.WriteLine(“The NET NET of DOTNET is that it ROCKS!!”); 


}


}






Now, when we run the main application, the common language runtime has to load the module that contains the ‘OutputMessage’ class. This enables the main application to create an instance of this class and call functions on it. This is done by a component of the runtime called the Class Loader. The job of the class loader is to locate the appropriate version of the module that contains the classes needed by the application. It can search for these modules in the application directory, in a directory specified in a configuration file, a shared location called the Global Assembly Cache or

GAC, or download it from a site.

The other interesting feature of this program is that even though the programmer called ‘new’ to allocate the instance of class ‘OutputMessage’ she did not call ‘delete’ to reclaim the memory. This may not be new to VB programmers but is certainly new to C++/COM programmers. One of the most common bugs occurs in a C++/COM application when it neglects to free one of the resources, like memory, causing that application or others to perform improperly or crash at some unpredictable time. The CLR automatically tracks resource usage, guaranteeing that your application never leaks resources. This facility is called automatic resource management or Garbage Collector. The runtime has a high performance, scalable garbage collector that frees up memory only when it requires additional memory. This ensures that the performance of the application is not adversely affected. In fact, in some scenarios the garbage collector can improve the performance of the application by locating frequently used objects nearby in memory.

Advertisment

Let us refine this program further to illustrate two important features of

CLR, namely, error-handling and multi-threading. Figure 6 shows the same application as Figure 4 but this time it spawns a thread which can execute in parallel with the main thread. Each of these threads, outputs a message to the console and exits.

Figure 6:



*Allow easy reference to classes in the System namespace*


using System;


*Refer to the external class that will output to the console*


using OutputMessage;


*Refer to the threading classes


using System.Threading;


* This “class” exists only to house the application’s entry-point function*


class MainApp {


*Static method called “Main” is application’s entry point function*


public static void Main() { 


*Try to create two threads which output to the console. In case of an*


error an exception is raised.*


try { 


*Create an instance of a class that will output to the console*


OutputMessage outMsg = new OutputMessage();


*PrintMessage is the secondary thread’s entry point.*


Thread t = new Thread(new ThreadStart(outMsg.PrintMessage));


*Start the thread* t.Start();


*Wait for the thread to exit t.Join();


Console.WriteLine(“The secondary thread has terminated.”);


}


*Catch errors via exceptions..*


catch(OutOfMemoryException e) {


Console.WriteLine(“Ouch.. ran out of memory…” + e.StackTrace);


}


}


}

























The “Thread = new Thread(….” line creates a new thread of execution which will start executing at the function called PrintMessage. The call to Start() in the next line actually starts the execution. The creation, execution, synchronization, and scheduling of threads is done by the runtime and is transparent to the developer.

Lastly, let us look at how error-handling is done by the runtime. In Win32, the error-handling mechanism is inconsistent. Sometimes, you deal with HRESULTs, at other times Win32 error codes and still others raise exceptions. In .Net, all the error-handling is done in a uniform manner via exceptions. This greatly simplifies writing, reading, and maintaining code. In addition, exceptions work across module and language boundaries as well. In the above program, if the code inside the try {… } block throws an exception then the runtime figures out which exception handler to execute. In this case, for out of memory exceptions, the handler is the catch{..} block right after the try block. The code inside the catch block is automatically executed once an exception is thrown, and the programmer does not have to do anything special.

We’ll discuss in future, the advanced services like security and interoperability that are provided by

CLR.

Tarun Anand is technical evangelist, Microsoft India

Advertisment