Section 2.1. Language Independence: The CLR


2.1. Language Independence: The CLR

The .NET CLR provides a common context within which all .NET components execute, regardless of the language in which they are written. The CLR manages every runtime aspect of the code, providing it with memory management, a secure environment to run in, and access to the underlying operating system services. Because the CLR manages these aspects of the code's behavior, code that targets the CLR is called managed code. The CLR provides adequate language interoperability, allowing a high degree of component interaction during development and at runtime. This is possible because the CLR is based on a strict type system to which all .NET languages must adhereall constructs (such as classes, interfaces, structures, and primitive types) in every .NET language must compile to CLR-compatible types. This gain in language interoperability, however, comes at the expense of the ability to use languages and compilers that Windows and COM developers have been using for years. The problem is that pre-existing compilers produce code that doesn't target the CLR, doesn't comply with the CLR type system, and therefore can't be managed by the CLR.

To program .NET components, you must use one of the .NET language compilers available with the .NET Framework and Visual Studio. The first release of Visual Studio (version 1.0, called Visual Studio .NET 2002) shipped with three new CLR-compliant languages: C#, Visual Basic .NET, and Managed C++. The second version (version 1.1, called Visual Studio .NET 2003) contained J# (Java for .NET). The third release of Visual Studio (version 2.0, called Visual Studio 2005) provided extensive language extensions, such as generics, supported by both C# 2005 and Visual Basic 2005. Third-party compiler vendors are also targeting the CLR, with more than 20 additional languages, from COBOL to Eiffel.

2.1.1. Intermediate Language and the JIT Compiler

One detail that often confuses .NET novices is how transformations are made from high-level languages (such as C# or Visual Basic) to managed code to machine code. Understanding this process is key to understanding how .NET provides support for language interoperability (that is, the core principle of language independence), and it has implications for binary compatibility. Although this book tries to steer away from most underlying implementation details and instead focus on how to best apply .NET, a brief overview of the CLR code-generation process goes a long way toward demystifying it all. In addition, understanding the .NET code-generation process is key to dealing with some specific security issues (discussed further in Chapter 12).

Compiling .NET managed code is a two-phase process. First, the high-level code is compiled into a language called intermediate language (IL). The IL constructs look more like machine code than a high-level language, but the IL does contain some abstract concepts (such as base classes and exception handling), which is why the language is called intermediate. The IL is packaged in either a DLL or an EXE. The main difference between an EXE and a DLL assembly in .NET is that only an EXE can be launched directly, while both DLLs and EXEs can be loaded into an already running process (more on that later). Because the machine's CPU can execute only native machine code, not IL, further compilation into actual machine code (the second phase) is required at runtime. Just-in-Time (JIT) compiler handles this compilation.

When the high-level code is first compiled, the high-level language compiler does two things: first it stores the IL in the EXE or the DLL, and then it creates a machine-code stub for every class method. The stub calls into the JIT compiler, passing its own method address as a parameter. The JIT compiler retrieves the corresponding IL from the DLL or EXE, compiles it into machine code, and replaces the stub in memory with the newly generated machine code. The idea is that when a method that is already compiled calls into a method that isn't yet compiled, it actually calls the stub. The stub calls the JIT compiler, which compiles the IL code into native machine code. .NET then repeats the method call to actually execute the method. Subsequent calls into that method execute as native code, and the application pays the compilation penalty only once per method actually used. Methods that are never called are never compiled.

When the compiler generates an EXE file, its entry point is the Main( ) method. When the loader loads the EXE, it detects that it is a managed EXE. The loader loads the .NET runtime libraries (including the JIT compiler) and then calls into the EXE's Main( ) IL method. This triggers compilation of the IL in the Main( ) method into native code in memory, and the .NET application starts running. Once compiled into native code, it can call other native code freely. When the program terminates, the native code is discarded, and the IL will need to be recompiled into native machine code by the JIT compiler the next time the application runs.

JIT compilation provides a number of important benefits. As you will see later in this chapter, JIT compilation offers .NET developers late-binding flexibility combined with compile-time type safety. JIT compilation is also key to .NET component binary compatibility. In addition, if all the implementations of the .NET runtime on different platforms (such as Windows XP and Linux) expose exactly the same standard set of services, then at least in theory, .NET applications can be ported between different platforms.

You might be concerned with the JIT compilation performance penalty. However, this concern should be mitigated by the fact that the JIT compiler can generate code that runs faster than code generated by traditional static source code compilers. For example, the JIT compiler looks at the exact CPU type (such as Pentium III or Pentium IV) and takes advantage of the added instruction sets offered by each CPU type. In contrast, traditional compilers have to generate code that targets the lowest common denominator, such as the 386-instruction set, and therefore can't take advantage of newer CPU features. Future versions of the JIT compiler may be written to keep track of the way an application uses the code (frequency of branch instructions used, forward-looking branches, etc.) and then to recompile in order to optimize the way the particular application (or even a particular user!) is using the components. The JIT compiler can also optimize the machine code it generates based on actual available machine resources, such as memory or CPU speed. Note that these advanced options aren't yet being implemented, but the mechanism has the potential of providing all of them. In general, JIT compiler optimization is a trade-off between the additional compilation time required and the increased application performance that results, and its effectiveness depends on the application calling patterns and usage. In the future, this cost can be measured during application installation or looked up in a user preferences repository.

2.1.2. .NET Programming Languages

The IL is the common denominator for all .NET programming languages. At least in theory, equivalent constructs in two different languages should produce identical IL. As a result, the traditional performance and capability attributes that previously distinguished between programming languages don't apply when comparing .NET languages. However, that doesn't imply that it doesn't matter which language you choose (as discussed later).

Native Image Compilation

As an alternative to JIT compilation, developers can compile their IL code into native code using a utility called the Native Image Generator (Ngen.exe). For example, the Windows Forms framework is installed in native image form. Native images are stored automatically in a special global location called the native image cache, a dedicated per-machine global storage. When searching for an assembly to load, the assembly resolver first checks the native image cache for a compatible version, and if one is found, the native image version is used. You can use Ngen.exe to generate native images during deployment and installation, to take advantage of potential optimizations done on a per-machine basis. The primary motivation for using Ngen.exe is to avoid the JIT compiler's performance penalty, which may be a reason for concern in a client application. You don't want a user-interface application to take more than a second or two to load, nor do you want the end user to wait after selecting a menu item or clicking a button for the first time. The downside is that using native images voids most of the benefits of JIT compilation.

Another Ngen.exe liability is that it adds a step to your installation program. Deploying .NET applications can be as easy as copying the EXEs and DLLs to the customer's machine, but you lose that simplicity with Ngen.exe. I recommend using Ngen.exe only after careful examination of your case and when you are convinced that there is a performance bottleneck in your application specifically caused by the JIT compiler. The easiest way to verify that is to run your profiling and test suite against both a JIT-compiled version and a native image version of the application, and compare the measurements.


The fact that all .NET components, regardless of language, execute in the same managed environment, and the fact that all constructs, in every language, must compile to a predefined CLR-compatible type, allows a high degree of language interoperability. Types defined in one language have equivalent native representation in all others. You can use the existing set of CLR types that are supported by all languages, or define new custom types. The CLR also provides a uniform exception-based error-handling model: an exception thrown in one language can be caught and handled in another, and you can either use the predefined set of CLR exception classes or derive and extend them for a specific use. You can also fire events from one language and catch them in another. Furthermore, because the CLR knows only about IL, security permissions and security demands travel across language barriers. The CLR has no problem, for example, verifying that the calling client has the right permissions to use an object even if they were developed in different languages.

2.1.3. .NET Components Are Language-Independent

As explained in Chapter 1, a core principle of component-oriented programming is language independence. When a client calls methods on an object, the programming language that was used to develop the client or the object should not be taken into account and should not affect the client's ability to interact with the object. Because all .NET components are compiled to IL before runtime, regardless of the higher-level language, the result is language-independent by definition. At runtime, the JIT compiler links the client calls to the component entry points. This sort of language independence is similar to the language independence supported by COM. Because the .NET development tools can read the metadata accompanying the IL, .NET also provides development-time language independence, which allows you to interact with or even derive from components written in other languages. For example, all the .NET Framework base classes were written in C# but are used by both C# and Visual Basic developers.

2.1.4. Choosing a .NET Language

Visual Studio 2005 ships with four CLR-compliant languages: Visual C# 2005 (C# for short), Visual Basic 2005 (Visual Basic for short), J#, and Managed C++. Managed C++ is mostly for interoperability, porting, and migration purposes, as well as advanced cases; the majority of .NET developers choose to disregard it as a mainstream language. J# is for maintaining and porting of former J++ applications. Thus, the real question is: as a .NET developer, should you choose C# or Visual Basic 2005? The official Microsoft answer is that all .NET languages are equal in performance and ease of use, and therefore choosing a language should be based on personal preference. According to this philosophy, if you come from a C++ background, you will naturally pick up C#, and if you come from a Visual Basic 6 (VB6) background, you will select Visual Basic 2005. However, I believe that basing the decision merely on your past language experience is not the best approach, and that you have to look not only at where you are coming from, but also at where you are heading.

Although C# and VB.NET and their respective development environments were practically identical in the first release of .NET, the third release of .NET (version 2.0) and Visual Studio 2005 introduced major differences between the environments, the programming models, and the programming experiences. C# introduced out-of-the-box code refactoring, code expansions, iterators, code formatting options, and numerous handy language features such as delegate inference. Visual Basic 2005 introduced the My object (in essence, a collection of global variables), VB6-like hiding of the Main( ) method, innovative project options and built-in implementation of many features, and task automation tools, all geared at enhancing developer productivity. The goal for Visual Basic 2005 was to approximate VB6 in its ease of use while allowing developers to generate applications as fast as possible. While both languages introduced generics support, it was the prime feature for the C# team and only a later addition to the Visual Basic 2005 product. Conversely, while both languages introduced edit-and-continue, it was the prime feature for the Visual Basic 2005 product and only a last-minute addition to C#.

I believe this trend will continue as the two languages continue to evolve to better serve their different markets and needs, and will only intensify in future releases of .NET. This is because the development community today is roughly divided into two types of development-tools users: the rapid application developers and the enterprise (or system) developers. The two communities are distinct not only because of the different languages they use, but primarily because of the different types of applications they develop and methodologies they employ.

Being first to market and developing the application as rapidly as possible often comes at the expense of long-term maintainability. In addition, these goals require different sets of skills, quality control practices, and management mentality. Traditionally, the rapid application development (RAD) market was ruled by VB6 and its predecessors. With relatively low skills, developers could produce applications quickly and effectively. VB6 did a great job of encapsulating the underlying Windows programming model and the interaction with COM. The problem with VB6 was that at a certain functional point of the application, you hit the VB6 glass ceilingthere were things you simply could not do in VB6 without either resorting to tools outside the environment or having extraordinary knowledge of the internal workings of VB6. This skill curve is shown in Figure 2-1.

Figure 2-1. The skill versus functionality of development tools


Languages such as C++ and technologies such as MFC and COM were unlimited in their capabilities, from multithreading to object-oriented modeling to Windows message hooking, and new functionality was usually gained at an incremental cost in skill. However, even the most trivial MFC application required a considerable amount of skill, and more complex applications were often beyond the reach of most developers. .NET offered a clean slate, substantially lowering the entry barrier compared with C++, and there was no glass ceiling in .NET: .NET's advantages and programming models are analogous to those of COM and MFC. C++ and COM developers love .NET because it makes everything so much easier. Advanced VB6 developers like .NET because instead of needing to go through the roof skill-wise to develop the required advanced functionality, they can linearly acquire new skills to add functionality. The question that arises is how to address the area exemplified in Figure 2-1 between the .NET curve and the VB6 curve. Unfortunately, most VB6 developers are in that area of the chart. .NET currently has little to offer to them, because for RAD purposes they are better off with VB6, rather than .NET with its higher skill and capability requirements.

I believe Microsoft is well aware of this situation, and that to better serve its target audience, future versions of Visual Basic will evolve to cover this missing area in the skill/functionality curve. The other side of the coin is that amortized over five or seven years of an enterprise application's lifecycle, the time saved using a RAD tool is insignificantthe real question is the cost of long-term maintenance. Applying component-oriented analysis and design methodologies in a large application requires great deal of skill and time and will result in unmaintainable code if done poorly. Moreover, the larger the application, the more likely you are to wish to maintain it for longer period of time, in order to maximize the return on the investment of development. Maintenance and extensibility have much to do with proper design, architecture, and abstractions, as well as component and interface factoring. Enterprise developers tend to spend the bulk of their time focusing on the code itself, rather than on the wizards and the surrounding tools. For enterprise developers, Microsoft designates C#, with its focus on code structure and constructs. Since a typical enterprise application debug session involves contemplative, time-consuming analysis of the problem, it's no wonder that the C# team had little regard for edit-and-continue capabilities.

I believe that if you are a RAD developer, Visual Basic 2005 is the tool for you, even if you're coming from a C++/MFC background. If you are an enterprise developer, where the cost of long-term maintenance is the overwhelming factor, you should choose C# even if you have a VB6 background.



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

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