|I l @ ve RuBoard|
Currently, the .NET framework will run on the following operating systems: Windows 98, Windows ME, Windows 2000, and Windows XP. (We expect that this list of operating systems will eventually include third-party O/Ss, so we recommend that you check "http://msdn.microsoft.com/net/" to check for any new additions to that list.)
From a high-level view, the .NET Framework can be described as a little virtual operating system that runs on top of one of the operating systems we mentioned. A closer inspection would reveal that the framework is made up of two main components : the .NET Class Library and the Common Language Runtime (CLR).
The .NET Class Library is a huge organized hierarchy of class objects, shown in Figure 1.1. These objects expose services that you can use to develop your own services. They include support for things like Windows Forms and Web Services, as well as objects for working with XML and data. To include these services in our applications, we navigate the hierarchy using traditional object-oriented programming principles. Navigating this hierarchy is a lot like you would navigate hierarchy of files and folders on your hard drive. For example, if you were referring me to a certain SQL driver on my system ( assuming we have the same OS) you would use something like "c: \WINNT\System\Data\SQLClient\". A similar reference included in your code would look like "System.Data.SQLClient". The only difference is that the object-oriented code references separate each level of the hierarchy with a dot "." These explicit references to groups of classes within the Framework's class libraries are also referred to as namespaces in .NET. You can think of namespaces as organizing classes just as folders organize files in a file system.
Figure 1.1. The .NET Framework's Architecture.
A namespace is a unique hierarchical reference to a specific class or group of similar classes. For example, the base classes that provide the services that support the runtime environment are found in the System namespace. This namespace includes services for things like I/O, security, data, and Web- related operations. To access these namespaces programmatically, your reference would look like System.Web or System.Data . The more specific a namespace is, the more specific its services will usually be. For example, if I wanted to connect to an SQL database, I could use the System.Data. SqlClient namespace.
We also use the benefits of a unique class reference so that two objects with the same name cannot clash . Imagine that you have an application that allows remote clients to call Web services in your Customers.Customer object. If that remote client has a local reference to its own version of an object named Customers.Customer , there will be some major problems for that remote client. Microsoft recommends that each developer create at least two unique namespace levels that become the root references for all their object creations. For example, a much better reference to my application's " Customer class" might look something like this:
The last component in the framework that at least deserves an honorable mention as a "main component" ( especially in this book) is a .NET runtime host. A runtime host is an application that loads the .NET runtime into its process. ASP.NET is an example of an application that loads the runtime into its process for its Web-related services. Internet Explorer is another example of a runtime host allowing us to download and execute managed code within its processes. The last runtime host component included in the framework is shell executables. This piece is actually what calls the .NET runtime from your operating system when you want to start a .NET application. It will also transfer the control of the .NET application from your system to the runtime.
The Common Language Runtime: A New Interpretation of Older Technology
The Common Language Runtime has many similarities to the runtime environment that executes Java applications. You'll learn later on that there are just as many differences as there are similarities, but it provides a good frame of reference for learning about a managed code execution environment.
To understand how the runtime works, let's discuss it in terms of an imaginary Web Service application you have just finished coding within Visual Studio .NET. VS .NET will handle all the dirty details of what needs to be passed to the appropriate compiler for your code. In earlier versions of Visual Studio, each language had its own similar-looking IDE, but was a separate application. In VS .NET, you have one unified development environment that can call a number of different languages' compilers. Alternatively, you can pass these specific command-line arguments to the compiler on your own using the appropriate syntax at the command-line prompt. This will involve a bit more research and studying on your part, but if you insist on this approach, at least do yourself a favor and download an evaluation version of VS .NET. If nothing else, the evaluation version will help you learn what it will pass to the compiler along with your code. In order to compile a simple C# application, a command line call to the compiler might look like:
Each programming language has its own compiler. Every one of these compilers must adhere to a common set of strict rules found at the core of the CLR. These strict rules ensure that each language's compiler will produce the same type of compiled code. Even though your compiled .NET application will have the same extension as a traditional Win32-based executable (that is, EXE or DLL), the internal results of the files are completely different. A .NET executable is compiled into what could be described as an "executable package" that includes Microsoft's Intermediate Language (MSIL or IL), metadata, an assembly manifest, and any additional assemblies or files the application makes reference to. In .NET, your application's executable is more commonly referred to as an assembly. An assembly is the compiled code that you will distribute to clients.
Before we discuss what happens to the assembly when a client launches the application, let's take a closer look at the internal components that make up a .NET assembly.
Intermediate Language and Metadata
Intermediate Language (IL) is .NET's version of compiled code. Whether you are compiling an ASP.NET DLL written in COBOL, or a Windows Forms EXE written in C#, the result is always this common self-describing intermediate language. IL is a simple text-based syntax that is complemented by a self-describing component called metadata. The combination of these two technologies gives .NET's managed runtime the ability to perform more operations in less time with less overhead.
The similarities between .NET and Java's managed runtime model will often draw comparisons with .NET's intermediate language and Java's byte code. This similarity comes from the benefits of a common compiled code that is capable of running on any machine that supports their respective runtime environment. In terms of their form and function, however, the two are completely different. .NET's compiled code (IL and metadata) offers some significant advantages over Java's integer-based byte code compilations.
.NET applications are Just-In-Time compiled a second time into native machine code. This is the same type of machine language code a 32-bit C++ executable would be compiled into. An assembly's descriptive text-based syntax allows the runtime the ability to intelligently compile an application into the most efficient set of native machine instructions for a given system. The .NET runtime also gives the developer compilation options to instruct the runtime to dynamically Just-In-Time (JIT) compile their application to machine language, or pre-compile everything to native machine code during installation. For example, if you want to avoid the overhead involved with JIT compiling, you can instruct the runtime to compile your application to native machine code during its initial set-up and installation process.
Java applications are compiled into an integer-based syntax that is never compiled into native machine code. Instead, the Java runtime uses a process that interprets its byte code compilations dynamically. Since an interpreter is essentially software that emulates a CPU, there is additional overhead in supporting this type of design.
In terms of performance differences, a recent benchmark comparison of the J2EE vs .NET (released by Microsoft while .NET was still in Beta) demonstrated that .NET greatly outperforms J2EE at almost every level while using only a fraction of the CPU's resources that J2EE required.
.NET precompiles its applications into native machine language with the help of an assembly's metadata. Metadata is XML that thoroughly describes the location and purpose of every object, property, argument, delegate, and method within a .NET application's assembly. Metadata eliminates the overhead associated with interpreting and managing the unknown. The runtime also uses metadata to validate the accuracy and purpose of each function to avoid errors, optimize memory management, and protect the user from malicious attacks.
Figure 1.2 shows the process of compiling an application into an assembly. While coded primarily in C#, you'll notice it includes references to files coded in many other languages.
Figure 1.2. Compiling a .NET application into an assembly.
.NET's compiled code also addresses something commonly referred to as "DLL Hell." In the past, developers would have to tweak and build multiple versions of their Win32 applications to create compatible executables for each category of popular system configurations. All it would take to destroy all your brilliant efforts is for another application to upgrade a shared system DLL to a newer version. .NET developers will not have to struggle with these issues any more, but .NET has costs associated with it that Win32 apps don't have. In other words, you should not expect your managed .NET applications to run as quickly as similar unmanaged Win32 applications. Microsoft's stance is that IL's self-describing code will eventually allow the runtime to compile native code so specific to an end user's system that it will easily be able to outperform Win32 applications. And of course, ASP.NET runs several times faster than classic ASP, so it is by no means slow.
The true inner beauty and power of every compiled .NET application is its Metadata. It is a " novel " solution that preserves the benefits of a managed code environment but uses new technology to overcome the performance losses associated with it. The idea is that the compiled code should read like a novel but has no room for interpretation, ambiguity, and assumptions: "Nothing but the facts, please ." This enables the CLR to quickly compile (yes, there is a second compilation) and manage the safest and fastest execution environment possible.
Metadata is really nothing more than XML code that describes the entire contents of the assembly. When a user starts a .NET application, the runtime will access the assembly manifest, which is made up of metadata that describes the specific contents of the package. It's like the table of contents for a .NET application. The manifest is also the runtime's main entry point for gathering and examining the data it needs to begin compiling the assembly into native machine language. You can also access this information via a disassembler included with the framework called ILDASM.EXE. This tool presents the high-level details of an assembly in a readable and organized hierarchy that describes the specific details of every object, method, argument, and property in your application. If you haven't done your homework, you might think this is Microsoft's consummate security blunder. Without digging into the specific details right now, suffice to say that this is not a blunder, and the intellectual integrity of an application targeted for the .NET Framework is no more or less vulnerable to hackers than an executable compiled for any other platform.
The JIT Compiler
Another critical component in the .NET Framework is the JIT (Just-In-Time) compiler (also known as "the JIT-ter"). This is the piece that actually compiles the contents of the assembly into machine language. It will not compile the entire assembly into memory all at once. Its initial responsibility is to examine the manifest to identify the critical pieces to compile and load to ensure that the application opens quickly. Beyond that point, the JIT compiler will only load the pieces of the application that it needs. The final product of this "just in time" approach is a runtime environment with more available memory space to process what is being used, faster. Microsoft believes that this approach will also guarantee that .NET applications will be able to outperform their much older, unmanaged, and occasionally delinquent cousin, the Win32 executable.
Let's take a look at components within the Common Language Runtime that prepare, compile, and manage a .NET application.
|I l @ ve RuBoard|