The Common Language Runtime


The CLR is one of the most radical features of .NET. Modern programming languages like VC++ and VB have always had runtimes . These are sometimes very small, like MSCRT40.DLL (used by Visual C++ applications), and other times, they can be quite big, like MSVBVM60.DLL (used by Visual Basic 6).

A language runtime's role changes depending on the language; it may actually execute the code (as in the case of Java, or VB applications compiled using p-code ), or in the case of native compiled languages (like C/C++), may provide common functionality used by the application. Some of this runtime functionality may be used directly by an application (such as searching for a character sequence in a string), or indirectly by a compiler that injects additional code during the compilation process to handle error situations or exceptions (such as a user aborting an application).

The CLR is a runtime for all .NET languages. It is responsible for executing and managing all code written in any language that targets the .NET platform.

The role of the CLR in some ways is similar to Sun's Java Virtual Machine (JVM) and the VB runtime. It is responsible for the execution of code developed using .NET languages. However, the critical point that differentiates the CLR is that it natively compiles all code. Although .NET compilers emit Intermediate Language (IL) code rather than pure machine code, the IL is JIT-compiled before code is executed. IL is not interpreted, and it is not byte code like the p-code used by VB or the code used by Java. IL is a language. It is compiled, converted into machine code, and then executed. The result is that applications targeting .NET, and which execute on the CLR, have exceptionally good application performance.

To complement IL, compilers that target the CLR also emit rich metadata that describes the types contained with a DLL or EXE (similar to COM type libraries but much richer) and version/dependency information. This metadata allows the CLR to intelligently resolve references between different application files at runtime, and also removes the dependency on the system registry. As we discussed earlier, these are two common problem areas for Windows DNA applications.

CLR Services

The CLR provides many core services for applications, such as garbage collection, code verification, and code access security. The CLR can provide these services due to the way it manages code execution, and the fact that “ thanks to the rich metadata compilers produce “ it can understand all types used within code.

Garbage collection is a CLR feature that automatically manages memory on behalf of an application. You create and use objects, but do not explicitly release them. The CLR automatically releases objects when they are no longer referenced and in use. This eliminates memory leaks in applications. This memory management feature is similar in some ways to how VB works today, but, under the hood, the implementation is radically different and much more efficient. A key difference is that the time at which unused memory will be released is non-deterministic . One side effect of this feature is that you cannot assume an object is destroyed when it goes out of the scope of a function. Therefore you should not put code into a class destructor to release resources. You should always release them in the code using a method, as soon as possible.

Code verification is a process that ensures all code is safe to run prior to execution. Code verification enforces type safety, and therefore prevents code from performing illegal operations such as accessing invalid memory locations. With this feature it should not be possible to write code that causes an application to crash. If code does something wrong, the CLR will throw an exception before any damage is inflicted. Such exceptions can be caught and handled by an application.

Code access security allows code to be granted or denied permissions to do things, depending on the security configuration for a given machine, the origins of the code, and the metadata associated with types that the code is trying to use. The primary purpose of this feature is to protect users from malicious code that attempts to access other code residing on a machine. For example, with the CLR, you could protect your applications and users when writing an e-mail application by denying all rights to code contained within an e-mail so that it cannot use other classes such as the address book or file system.

The code produced for an application designed to run under the CLR is called managed code “ self- describing code that makes use of and requires the CLR to be present. Code written with languages like VB6 that doesn't provide IL and doesn't need the CLR to run is called unmanaged code . For managed code, the CLR will:

  • Always locate the metadata associated with a method at any point in time.

  • Walk the stack.

  • Handle exceptions.

  • Store and retrieve security information.

These low-level requirements are necessary for the CLR to watch over code, provide the services we've discussed, and ensure its integrity for security and protection reasons.

Common Functionality

The CLR provides access to common base functionality (such as string searching) for all languages via the Base Class Library (BCL) . The CLR is basically a replacement for the WIN32 API and COM. It provides the foundation on which the .NET vision has been realized, since most of the Windows DNA limitations stem from features of these technologies. More importantly for VB developers, the WIN32 API provided a lot of functionality they could not easily use previously, such as process creation and free-threaded support. Since that functionality is now part of the CLR, VB (and other languages such as COBOL) can now create high-performance multi-threaded applications.

The CLR is object-oriented. All of the functionality of the CLR and the class libraries built on top of it are exposed as methods of objects. These classes are as easy to use as the ASP intrinsic objects and ADO objects you used previously, but are far richer in functionality.

Using Objects

To see how to create and use objects with the CLR, let's walk through some simple examples. In these, you will see how to create:

  • A class in VB.

  • A class in C# that inherits from the VB class.

Let's start by creating a simple VB class:

  Namespace Wrox.Books.ProASPNet   Public Class MyVBClass     End Class   End Namespace  

Don't worry about what the Namespace statement means for the moment “ we'll come to that next .

Assuming this class is contained in a file called base.vb , you can compile it using the following command, which invokes the Visual Basic .NET command line compiler:

  vbc base.vb /t:library  

The output of the command is a DLL called base.dll that contains the MyVBClass class. The /t:library parameter tells the compiler that you are creating a DLL. Any other developers can now use your class in their own code written using their preferred language.

To show the in-class inheritance feature of the CLR, and to demonstrate how easy inheritance is, create a class in any other language (in this case C#) that derives from the base VB class. This C# class inherits all of the behavior (the methods, properties, and events) of the base class:

  using Wrox.Books.ProASPNet;   public class MyCSharpClass : MyVBClass   {   }  

Assuming this is contained in a file called derived.cs , compile it using the following command, which invokes the C# command line compiler:

  csc /t:library /r:base.dll derived.cs  

csc is the name of the C# compiler executable. /t:library ( t is short for target) tells the compiler to create a DLL (referred to as a library). /r:base.dll ( r is short for reference) tells the compiler that the DLL being created references types (classes etc.) in the DLL base.dll .

Note

You'll find a more comprehensive introduction to the syntax changes in the new .NET languages, as well as the similarities and differences between them, in the next chapter. We'll also see more on the syntax of the compiler options, and discuss JScript.NET.

This simple example doesn't have any real practical use (since there are no methods, etc.), but hopefully it goes some way in demonstrating how easy and simple building components with the CLR and Visual Basic .NET and C# is.

This code example is so clean because the CLR is effectively responsible for all of the code. At runtime, it can hook up the classes to implement the inheritance in a completely seamless way. Cross-language development and integration couldn't really be any simpler. You can create classes in any language you want, and use them in any other languages you want “ either instantiating and using them, or, as in the previous example, actually deriving from them to inherit base functionality. You can use system classes (those written by Microsoft) or third-party classes in exactly the same way as you would any other class written in your language of choice. This means the entire .NET Framework is one consistent object- oriented class library.

Namespaces

In the next chapter, you'll create and use many different types when writing a CLR application. Within your code, you can reference types using the Imports keyword in Visual Basic .NET, and the using keyword in C#. In the VB class definition, there was the following line:

  Namespace Wrox.Books.ProASPNet  

The namespace declaration tells a compiler that any defined types (such as classes or enumerations) are part of the specified namespace, which in this case is called Wrox.Books.ProASPNet . If you want to use these classes in another application, you need to import the namespace. In C# code, you saw this namespace import definition defined as:

  using Wrox.Books.ProASPNet;  

This namespace import declaration makes the types within the Wrox.Books.ProASPNet namespace available to any of the code in your C# file.

Important

In .NET, everything is referred to as a type. A type can be a class, enumeration, interface, array, or structure.

Namespaces have two key functions:

  • They logically group related types :For example, System.Web contains all ASP.NET classes that manage the low-level execution of a Web request. System.Web.UI contains all of the classes that actually render UI, and System.Web.Hosting contains the classes that aid in ASP.NET being hosted inside IIS or other applications.

  • They make name collision less likely :In an object-oriented world, many people are likely to use the same class names. The namespace reduces the likelihood of a conflict, since the fully qualified name of a class is equal to the namespace name plus the class name. You can choose to use fully qualified names in your code, and forgo the namespace import declaration. However, you'll typically only do this if you have a namespace that contains the classes that ASP.NET uses for hosting inside IIS or the other applications.

    Important

    Namespaces do not physically group types, since a namespace can be used in different assemblies (DLLs and EXEs).

Namespaces are used extensively in the CLR, and since ASP.NET pages are compiled down to CLR classes, they will be used extensively in your ASP.NET pages. For this reason, ASP.NET automatically imports the most common names into an ASP.NET page. As you'll see later in the book, this list can be extended to include your own classes. It helps to think of namespaces as directories. Rather than containing files, they contain classes. However, a namespace called Wrox.MyBook does not mean a namespace called Wrox exists. It is likely that it does, but it is not mandatory.

A Common Type System

For interoperability to be smooth between languages, the CLR has a common type system. Languages like VB have built-in primitive types such as Integer , String , and Double . C++ has types like long , ulong , and char* . However, these types aren't always compatible. If you've ever written a COM component, you'll know that making sure your components have interfaces that are usable in different languages depends a great deal on types. You'll often spend a lot of time converting types, and it's a real pain.

For the CLR to make cross-language integration so smooth, all languages have to use a common type system. The following table lists the types that form part of the CLS , and defines the types usable in any language targeting the CLR:

Type

Description

Range/ Size

System.Boolean

Represents a Boolean value.

True or False . The CLS does not allow implicit conversion between Boolean and other primitive types.

System.Byte

Represents an unsigned byte value.

Positive integer between 0 and 255.

System.Char

Represents a UNICODE character value.

Any valid UNICODE character.

System.DateTime

Represents a date and time value.

IEEE 64-bit (8-byte) long integers that represent dates ranging from 1 January 1 CE (the year 1) to 31 December 9999 and times from 0:00:00 to 23:59:59.

System.Decimal

Represents positive and negative values with 28 significant digits.

79,228,162,514,264,337,593,543,950,335 to negative 79,228,162,514,264,337,593,543,950,335.

System.Double

Represents a 64-bit, double precision, floating point number.

Negative 1.79769313486231570E+308 to positive 1.79769313486231570E+308.

System.Int16

Represents a 16-bit signed integer value.

Negative 32768 to positive 32767.

System.Int32

Represents a 32-bit signed integer value.

Negative 2,147,483,648 to positive 2,147,483,647.

System.Int64

Represents a 64-bit signed integer.

Negative 9,223,372,036,854,775,808 to positive 9,223,372,036,854,775,807.

System.Sbyte

Represents an 8-bit signed integer.

Negative 128 to positive 127.

System.Single

Represents a 4-byte, single precision, floating point number.

Negative 3.402823E38 to positive 3.402823E38.

System.TimeSpan

Represents a period of time, either positive or negative.

The MinValue field is negative 10675199.02:48:05.4775808. The MaxValue field is positive 10675199.02:48:05.4775807.

System.String

Represents a UNICODE String.

Zero or more UNICODE characters .

System.Array

Represents a single dimension array.

Range is based upon declaration and usage. Arrays can contain other arrays.

System.Object

The base type from which all other types inherit.

N.A.

Different languages use different keywords to expose these types. For example, VB will convert variables you declare as type Integer to System.Int32 during compile time, and C# will convert int into System.Int32 . This makes working with different languages more natural and intuitive, while not compromising any goals of the CLR. You can of course declare the native CLR type names in any language, but you typically wouldn't do this unless the language did not have its own native mapping. This is another great feature of the CLR. If the language doesn't support a feature, you can usually find some .NET Framework classes that do.

All types derive from System.Object . This class has four methods (all of these methods are available on all types) as shown in the following table:

Method

Description

Equals

Allows two object instances to be compared for equality. Most CLR classes override this and provide a custom implementation. For example, System.ValueType has an implementation that compares all members ' fields for equality. Value types are discussed shortly.

GetHashCode

Returns a hash code for the object. This function is used by classes such as Hashtable to get a unique identity for an object. Two objects of the same type that represent the same value will always return the same hash code.

GetType

Returns a Type object that can be programmatically used to explore the methods, properties, and events of a type. This feature of the CLR is called reflection .

ToString

Returns a string representation of the type. The default implementation returns the fully qualified type name suitable in aiding debugging. Most CLR types override this method and provide a more useful return value. For example, System.Int32 will return a string representation of a number.

Common Language Specification

Some languages can require types that other languages do not support. To allow for this, there are additional CLR types that support the functionality required for specific languages such as C#. However, one caveat with sharing CLR classes created with different languages means that there is the potential that a class will not be usable in certain languages if its types are not understood . For example, the C# language has types such as the unsigned long that are not available in languages such as VB, so a C# class that uses an unsigned long as part of its public member definitions cannot be used in VB. However, the same class can be used if such types are used in non-public member definitions . A common set of base types exists to help ensure types created by different languages are compatible. This is part of the CLS. Most CLR compilers have options to flag warnings if non-CLS types are used.

Everything in the CLR Is an Object

Every type in the CLR is an object. As seen in the previous table, all types are derived from System.Object. When you define your own custom types such as classes, structures, and enumerations, they are automatically derived from System.Object , even though the inheritance isn't explicitly defined. When a class is compiled, the compiler will automatically do this.

As every type has a common base type ( System.Object ), you can write some very powerful generic code. For example, the System.Collections namespace provides lots of collection classes that work with System.Object (for instance, the Hashtable class is a simple dictionary class populated using a name and a System.Object reference). By using either a name or an index, you can retrieve an object reference from the collection very efficiently . Since all types derive from System.Object , you can hold any type in a Hashtable .

Value Type and Reference Types

If you're a C/C++ developer or an experienced VB programmer, you're probably thinking that having every type in the CLR as an object is expensive, since primitive types such as Integer , Long , and Structure in VB6 and C/C++ only require space to be allocated on the stack, whereas object references require allocated space on the heap. To avoid having all types heap allocated, which would compromise code execution performance, the CLR has the following two types:

  • Value types : They are allocated on the stack , just like primitive types in VBScript, VB6, and C/C++.

  • Reference types : They are allocated on the managed CLR heap , just like object types.

It is important to understand how the CLR manages and converts these types using boxing and unboxing, to avoid writing inefficient code.

Value types are not instantiated using New (unless you have a parameterized constructor), and go out of scope when the function they are defined within returns. Value types in the CLR are defined as types that derive from System.ValueType . All of the CLR primitive types such as System.Int32 derive from this class, and when structures are defined using Visual Basic .NET and C# those types automatically derive from System.ValueType .

Here is an example Visual Basic .NET structure that represents a CLR value type called Person :

  Public Structure Person     Dim Name As String   Dim Age As Integer     End Structure  

The equivalent C# structure is:

  Public Struct Person   {   string name;   int age;   }  

Both compilers will emit IL that defines a Person type that inherits from System.ValueType . The CLR will therefore know to allocate instances of this type on the stack.

Boxing

When using CLR classes such as the Hashtable (discussed in Chapter 15) that work with collections of System.Object types, you need to be aware that the CLR will automatically convert value types into reference types. This conversion happens when you assign a value type, such as a primitive type like System.Int32 , to an Object reference, or vice versa.

The following code will implicitly box an Integer value when it is assigned to an Object reference:

  //C#   int i = 32;   Object o = i;   'VB   Dim i as Integer = 32   Dim o as Object = i  

When boxing occurs, the contents of a value type are copied from the stack into memory allocated on the managed heap. The new reference type created contains a copy of the value type, and can be used by other types that expect an Object reference. The value contained in the value type and the created reference types are not associated in any way (except that they contain the same values). If you change the original value type, the reference type is not affected.

The following code explicitly unboxes a reference type into a value type:

  //C#   Object o;   int i = (int) o;   'VB   Dim o as Object   Dim i as Integer = CType(o, Integer)  
Note

In this example, you need to assume the object variable o has been previously initialized .

When unboxing occurs, memory is copied from the managed heap to the stack.

Understanding boxing and unboxing is important, since it has performance implications. Every time a value type is boxed, a new reference type is created and the value type is copied onto the managed heap. Depending on the size of the value type, and the number of times value types are boxed and unboxed, the CLR can spend a lot of CPU cycles just doing these conversions.

To put all this type theory into practice, take a look at the following Visual Basic .NET code, which illustrates when value types and reference types are used, and when boxing and unboxing occurs:

  Imports System.Collections   Imports System  

Let's start by declaring a simple structure called Person that can hold a name and an age. Since structures are always value types (remember compilers automatically derive structures from System.ValueType ), instances of this type will always be held on the stack:

  Public Structure Person   Dim Name As String   Dim Age As Integer   End Structure  

Then begin your main module:

  Public Module Main   Public Sub Main()   Dim dictionary As Hashtable   Dim p As Person  

First, create a Hashtable . This is a reference type, so use New to create an instance of the object, which will be allocated on the managed CLR heap:

  dictionary = New Hashtable()  

Hashtable and other collections classes are explained in more detail in Chapter 15.

Next, initialize your Person structure with some values. This is a value type, so you don't have to use New , since the type is allocated automatically on the stack when you declare your variable:

  p.Name = "Richard Anderson"   p.Age = 29  

Once your Person structure is initialized, add it to the dictionary using a key of Rich . This method call will implicitly cause the value type to be boxed within a reference type, as we discussed, since the second parameter of the Add method expects an Object reference:

  dictionary.Add( "Rich", p )  

Next, change the values of the same Person structure variable to hold some new values:

  p.Name = "Sam Anderson"   p.Age = 28  

Then add another person to the dictionary with a different key. Again, this will cause the value type to be boxed within a reference type and added to the dictionary:

  dictionary.Add( "Sam", p )  

At this point, you have a Hashtable object that contains two reference types. Each of these reference types contains your Person structure value type, with the values we initialized.

You can retrieve an item from the Hashtable using a key. Since the Hashtable returns object references, you can use the CType keyword to tell Visual Basic .NET that the object type returned by the Item function is actually a Person type. Since the Person type is a value type, the CLR knows to unbox the Person type from the returned reference type:

  p = CType( dictionary.Item("Rich"), Person )  

And now you can use the Person type:

  Console.WriteLine("Name is {0} and Age is {1}", p.Name, p.Age )     End Sub   End Module  

Language Changes

The CLR supports a rich set of functionality. To enable languages like VB to make use of these capabilities, the language syntax has to be enhanced with new keywords. The CLR functionality may be common to all languages, but each language will expose the functionality in a way that suits that language. As the simple inheritance example showed earlier, both Visual Basic .NET and C# explicitly define the start and end of a class (VB used to do this implicitly via the file extension), but the keywords used are different.

Note

We'll investigate the language changes in detail in Chapter 3.

Assemblies “ Versioning and Securing Code

We discussed earlier how one of the biggest problems with Windows DNA applications was the number of implicit dependencies and versioning requirements. One application can potentially break another application by accidentally overwriting a common system DLL, or removing an installed component. To help resolve these problems, the CLR uses assemblies.

An assembly is a collection of one or more files, with one of those files (either a DLL or EXE) containing some special metadata known as the assembly manifest . The assembly manifest defines what the versioning requirements for the assembly are, who authored the assembly, what security permissions the assembly requires to run, and what files form part of the assembly.

An assembly is created by default whenever you build a DLL. You can examine the details of the manifest programmatically using classes located in the System.Reflection namespace. To keep our discussion focused, let's use a tool provided with the .NET SDK called Intermediate Language Disassembler (ILDASM) . You can run this tool either from a command prompt, or via the Start Run menu.

When the tool is running, you can use the File Open menu to open up the derived.dll created in the simple inheritance sample earlier. Once loaded, as shown in Figure 2-2, ILDASM shows a tree control containing an entry for the assembly manifest, and an entry for each type defined in that DLL:

click to expand
Figure 2-2:

Although the type information for MyCSharpClass is part of your DLL, it is not part of the assembly manifest. It is, however, part of the assembly. If you double-click on the MANIFEST entry, you'll see the manifest definition as shown in Figure 2-3:

click to expand
Figure 2-3:

The manifest is stored as binary data, so what you see here is a decompiled form of the manifest, presented in a readable form. The .assembly extern lines tell that this assembly is dependent upon two other assemblies “ base and mscorlib . mscorlib is the main CLR system assembly, which contains the core classes for the built-in CLR types, etc. You created the assembly base earlier when you built your file base.vb . The default name of the assembly created by a compiler reflects the output filename created, minus the extension. If you'd used the following command line to build your VB DLL from earlier:

  vbc base.vb /out:Wrox.dll  

The assembly created would be called Wrox , and the output from ILDASM for derived.dll would show .assembly extern line for Wrox instead of base .

In Figure 2.3, you can see that the reference to the base assembly from within the derived assembly has two additional attributes:

  • .ver : This specifies the version of the assembly compiled against. The CLR uses the Major:Minor:Build:Revision format for version numbers . An assembly is considered incompatible if the Major or Minor version numbers change.

  • .hash : A hash value that can be used to determine if any of the files in the referenced assembly are different.

The mscorlib assembly reference has one additional attribute worth briefly discussing:

  • .publickeytoken : This specifies part of a public key that can be used by the CLR when loading the reference assembly, to100% guarantee that only the named assembly written by a given party (in this case Microsoft) is loaded.

The .assembly derived section of the manifest declares the assembly information for the derived .dll file. This has attributes similar to the external manifests . By default the version of an assembly is 0.0.0.0 . To change this, it is necessary to specify an assembly version attribute in one of the source files. Visual Studio .NET typically creates a separate source file to contain these attributes, but you can define them anywhere you choose, providing it's not within the scope of another type definition.

In Visual Basic .NET the format for assembly attributes is:

  <assembly: AssemblyVersion("1.0.1.0")>  

This would make the assembly version appear as 1.0.1.0 . Within C# the format is slightly different, but has the same net effect:

  [assembly: AssemblyVersion("1.0.1.0")]  
Important

To use assembly attributes you have to import the System.Reflection namespace.

Attributes are a feature of CLR compilers that enables you to annotate types with additional metadata. The CLR or other tools can then use this metadata for different purposes, such as containing documentation, information about COM+ service configuration, and so on.

Are Assemblies Like DLLs?

For the most part, you can think of a DLL and an assembly as having a one-to-one relationship. Most of the time, you would probably use them this way, but for special cases you can use the more advanced features of the command line compilers to create assemblies that span multiple DLLs, a single EXE, and contain or link to either one or more resource files. Figure 2-4 shows the basic structure of a typical assembly:

click to expand
Figure 2-4:

This diagram illustrates the System.Web assembly. This assembly consists of a single file, System.Web.DLL . The System.Web.DLL contains the assembly manifest, the type metadata that describes the classes located within the System.Web.DLL file, the IL for the code, and resources for embedded images. The embedded resources within this System.Web.DLL are mainly images used in Visual Studio .NET to represent Web controls. If the designers of this assembly had chosen to do so, they could have created a multi-file assembly ( complex assembly ) as the one shown in Figure 2-5:

click to expand
Figure 2-5:

The System.Web assembly in this diagram consists of four files:

  • System.Web.Core.DLL :Contains the assembly manifest describing all the other files and dependencies of those files, the type metadata for the classes located within the file (not the whole assembly), the IL for the code within the classes, and some embedded resources.

  • System.Web.Extra.DLL :Contains the type metadata for the classes, etc., located within the file (not the assembly), and the IL for code within the various classes.

  • C1.BMP :A bitmap image.

  • C2.GIF :A Graphic Interchange Format (GIF) picture.

Although the physical file structure of these assemblies is different, to the CLR they have the same logical structure. Both expose the same types, and consumers of the types within the assemblies are not affected, since they only ever reference the types within it by name and not by physical location.

All files within an assembly are referred to as modules . A namespace can span one or more modules. A namespace can also span one or more assemblies. The physical structure of an assembly has no impact or relation to the namespace structure.

The reasons for creating a multi-file assembly vary. They may be appropriate if you are working in a large team and want to be able to bring individual developer's modules together within a single assembly that is version-controlled. More practically, you might want to manage memory consumption. If you know certain classes are not always needed, putting them into a separate physical file means the CLR can delay loading that file until the types within it are needed.

For most applications, using complex assemblies is probably overkill. Even if you don't use the versioning features assemblies provide, you should still be aware of how type-versioning within assemblies occurs.

It is necessary to try to make sure that your business components are kept compatible with the ASP.NET pages that call them. You create private assemblies when building component files like DLLs that are only used by your application. However, for the most part this is an implementation detail you can ignore. However, if you want to share component files across multiple web applications, you may need to create shared assemblies and put them in the GAC, and understand the more complex versioning implications they have.

No More DLL Hell

The CLR can load multiple versions of an assembly at the same time. This basically solves DLL hell “ the problem where installing a new application might break other applications, because newer DLLs are installed that older applications are not compatible with. This is known as side-by-side component versioning and deployment.

Type Versioning

If you have ever written a COM component, you have probably experienced binary-compatibility problems. This is typified by needing to change a component's public interface (adding, changing, or deleting methods) once it has already been released. The rules of COM say that once a component is released, its interface is immutable; it should not be changed. If you do need to make changes, your component should support multiple interfaces. This approach has a number of problems:

  • It's difficult to manage multiple interfaces, and almost impossible to not break compatibility by mistake.

  • VB6 never really supported interfaces properly, so implementing interfaces has always been a black art.

  • Most application developers don't want to version individual components.

Versioning in ASP web applications to date has shielded many developers from these problems. ASP pages always used late-binding to talk to COM components. Each time a method is called, the script engine executing the ASP page code determines whether the method exists, invoking it if it does. If a method does not exist, an error is raised.

This late-binding approach is actually pretty good in some ways. ASP developers don't have to worry too much about versioning “ as long as the methods called for each object are supported, the page will work. The only drawback of this approach, which can be a significant cost, is a small performance hit per method call to find out if a method exists.

The CLR provides late-binding versioning for types in a manner similar to late-binding in COM/ASP, but without a performance hit. When the CLR loads a type such as a class, it knows what methods of other types the class is going to call. It can therefore resolve these references at runtime at a method- level, generating early-bound code to call each method. This means that the programmer only has to worry about keeping method signatures the same in order to maintain binary compatibility. It is possible to support interfaces and use the same technique as COM, but Microsoft discourages this unless it is really necessary. If an interface changes, all classes implementing the interface have to be recompiled.

If you do break compatibility in a component, it will show itself quickly. For example, if an application calls into a component that no longer supports a method it needs, the CLR will raise a MissingMethodException . If an application tries to use a type that has been deleted, or a type that doesn't implement all of the required methods of an interface, a TypeLoadException will be thrown. These can be caught by the calling application, and they usually contain detailed messages to explain what went wrong.

.NET provides a platform that enables you to start implementing next generation Internet applications, with more power and speed than ever before. It also enables you to capitalize on your existing knowledge of VB, C/++, and JScript, as well as introducing enhancements (like inheritance, polymorphism, and multi-threading ) to all the languages “ not to mention the brand new language C#, designed to work from the ground up with the .NET Framework.

Note

The reason for not giving .NET DLL and EXE files a different extension is compatibility.

CLR and COM

At this point you're probably beginning to ask yourself where COM fits into the CLR. The simple answer is that it doesn't. From a technology perspective, COM is not used as a core technology to power the CLR. The CLR is new code from the ground up, and COM is only used for interoperability and hosting. Interoperability enables you to use existing COM components within .NET languages, just as if they were CLR classes, and to use CLR classes as COM components for non-CLR applications. Hosting the CLR inside applications (such as IIS) is achieved using COM, as the CLR provides a set of COM components for loading and executing CLR code.

The fact that the CLR is not COM-based signals that COM will no longer be a mainstream technology for web applications written on the Windows platform in the future. This means that most of the concepts that you may use as a C/C++ programmer, or may have been aware of as a VB programmer, are not part of the .NET Framework. For example, in the world of COM, all objects were reference-counted, and all COM methods returned an HRESULT . However, the concept of interface-based programming is still prevalent within .NET, so the COM way of thinking about things in terms of interfaces is still valid. But most people will probably switch from interfaces to base classes for productivity reasons.

Important

Internally, Microsoft is promoting the idea that teams should no longer build and release new COM components. The way forward is to create .NET components that are exposed through interop as COM components.

Intermediate Language

We're not going to cover IL in any great depth in this book, but to get a feel for it, consider this line of Visual Basic .NET code:

  System.Console.WriteLine(".NET makes development fun and easy again")  

It calls the WriteLine method of the Console class, which is part of the System namespace. A namespace, as you saw, provides a way of grouping related classes. When compiled and run as part of a console application, this line of code will basically output the simple message to a console window. You could write this same line of code in IL, as follows :

  ldstr ".NET makes development fun and easy again"   call void [mscorlib]System.Console::WriteLine(string)  

This code uses the same API call, except it is prefixed with [mscorlib] , which tells the CLR that the API we're calling is located in the file MSCORLIB.DLL . The first line shows the way in which parameters are passed to an API. The ldstr command loads the specified string onto the call stack, so the API being called can retrieve it.

If you took the simple VB code and used the Visual Basic .NET compiler to compile it, the output produced would be an executable file that contains IL similar to that just seen. The IL would not be identical since the VB compiler actually produces some additional initialization IL, which isn't important in this example. The important thing to note here is that the DLLs and EXEs created are self-describing .

Application Domains

All Windows applications run inside a process. Processes own resources such as memory and kernel objects, and threads execute code loaded into a process. Processes are protected from each other by the operating system such that one process cannot in any way unexpectedly affect the execution of another application running in another process. This level of protection is the ultimate way of having many applications run on a machine, safe in the knowledge that no matter what one application does, the others should continue as normal. Processes provide a high level of application fault tolerance, which is why IIS and COM+ use them when running in high isolation mode.

The problem with processes in Windows is that they are a very expensive resource to create and manage, and do not scale well if used in large numbers. For example, if you're using IIS and have a large number of web sites configured to run in isolation, each one has its own dedicated process that will consume a lot of resources (such as memory). If you run all these applications within the same process you'll be able to run many more web sites on one machine, since fewer resources are consumed. Less memory will be required since DLLs would only have to be loaded into one process, reducing the inter-process overhead between the various processes and the core IIS worker process. The downside to this approach is that if one site crashes, all sites will be affected. Figure 2-6 shows multiple application domains in the same .NET process:

click to expand
Figure 2-6:

Application Domains (AppDomains) in .NET have the same benefits as a process, but multiple application domains can run within the same process, as shown in Figure 2-6. Application domains can be implemented safely within the same process, because the code verification feature of the CLR ensures that the code is safe to run before allowing it to execute. This provides huge scalability and reliability benefits. For example, with .NET, applications like IIS can achieve higher scalability by running each web site in a different application domain, rather than a different process, safe in the knowledge that inter-application isolation has not been compromised.

When running as part of IIS4/5, ASP.NET uses application domains to run each instance of an ASP.NET application, as illustrated in Figure 2-7. Each ASP.NET application runs in its own application domain, and is therefore protected from other ASP.NET applications on the same machine. ASP.NET ignores the process isolation specified in IIS.

click to expand
Figure 2-7:

In IIS 6.0, as installed with Windows Server 2003, applications (and hence AppDomains) can be executed in separate application pools, as shown in Figure 2-8. This provides even better robustness, performance, and scalability. The allocation of each ASP.NET application or web site to a specific application pool is specified in IIS Manager. Application pools can also be used to run different versions of ASP.NET on the same box “ all the applications within an application pool must run under the same version, but you can create one or more application pools for each different version.

click to expand
Figure 2-8:

It's also possible to switch IIS 6.0 into a mode called IIS 5.0 Isolation Mode, in which case it works just like IIS 5.0 “ with a consequent loss of performance and robustness. See Chapter 14 for more details.




Professional ASP. NET 1.1
Professional ASP.NET MVC 1.0 (Wrox Programmer to Programmer)
ISBN: 0470384611
EAN: 2147483647
Year: 2006
Pages: 243

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net