To understand how your VB.NET applications work and just how much the code differs from the VB code that Dorothy wrote in Kansas, it's important to understand managed code and how it works. To use managed execution and get the benefits of the CLR, you must use a language that was built for, or targets, the runtime. Fortunately for you, this includes VB.NET. In fact, Microsoft wanted to make sure that VB.NET was a premier language on the .NET platform, meaning that Visual Basic could no longer be accused of being a "toy" language.
The runtime is a language-neutral environment, which means that any vendor can create a language that takes advantage of the runtime's features. Different compilers can expose different amounts of the runtime to the developer, so the tool you use and the language in which you write might still appear to work somewhat differently. The syntax of each language is different, of course, but when the compilation process occurs, all code should be compiled into something understandable to the runtime.
Just because a language targets the runtime doesn't mean that the language can't add features that are not understood by other languages. In order to make sure that your components are completely usable by components written in other languages, you must use only types that are specified by the Common Language Specification.
Microsoft Intermediate Language (MSIL)
One of the more interesting aspects of .NET is that when you compile your code, you do not compile to native code. Before you VB developers panic and fear that you are returning to the days of interpreted code, realize that the compilation process translates your code into something called Microsoft Intermediate Language, called MSIL or just IL. The compiler also creates the necessary metadata and compiles it into the component. This IL is CPU-independent.
The IL contains the metadata, and the final file is called a portable executable, or PE. The PE is also sometimes called a physical executable. Because the PE contains your IL and metadata, it is therefore self-describing, eliminating the need for a type library or interfaces specified with the Interface Definition Language (IDL).
The Just-In-Time Compiler
Your code does not stay IL for long, however. It is this IL that can be distributed and placed with the CLR running on the .NET Framework on any operating system because the IL is platform-independent. When you run the IL, however, it is compiled to native code for that platform. Therefore, you are still running native code; you are not going back to the days of interpreted code at all. The compilation to native code occurs via another tool of the .NET Framework: the Just-In-Time (JIT) compiler.
With the code compiled, it can run within the Framework and take advantage of such low-level features as memory management and security. The compiled code is native code for the CPU on which the .NET Framework is running, meaning that you are indeed running native code instead of interpreted code. A JIT compiler will be available for each platform on which the .NET Framework runs, so you should always be getting native code on any platform.
It is still possible to call operating systemspecific APIs, which would, of course, limit your application to just that platform. That means it is still possible to call Windows APIs, but then the code would not be able to run within the .NET Framework on a non-Windows machine. At this point in time, the .NET Framework exists only on the Windows platform, but this will change in the future.
Interestingly, the JIT complier doesn't compile the entire IL when the component is first called. Instead, each method is compiled the first time it is called. This keeps you from having to compile sections of code that are never called. After the code is compiled, of course, subsequent calls use the compiled version of the code. This natively compiled code is stored in memory in Beta 1. However, Microsoft has promised a pre-JIT compiler that will compile all the code at once and store the compiled version on disk, so the compilation would persist over time.
After the code is executing, it can take full advantage of the CLR, with benefits such as the security model, memory management, debugging support, and profiling tools. Most of these benefits will be mentioned throughout the book.
One of the new structures you will create in VB.NET is the assembly. An assembly is a collection of one or more physical files. The files are most often code, such as the classes you build, but they could also be images, resource files, and other binary files associated with the code. Such assemblies are known as static assemblies because you create them and store them on disk. Dynamic assemblies are created at runtime and are not stored to disk (although they can be).
An assembly represents the unit of deployment, version control, reuse, and security. If this sounds like the DLLs you have been creating in Visual Basic for the past six years, it is similar. Just as a standard COM DLL has a type library, the assembly has a manifest that contains the metadata for the assembly, such as the classes, types, and references contained in the IL. The assembly often contains one or more classes, just like a COM DLL. Applications are built using assemblies; assemblies are not applications in their own rights.
Perhaps the most important point of assemblies is this: All runtime applications must be made up of one or more assemblies.
The Assembly Manifest
The manifest is similar in theory to the type library in COM DLLs. The manifest contains all the information about the items in the assembly, including what parts of the assembly are exposed to the outside world. The manifest also lists the assembly's dependencies on other assemblies. Each assembly is required to have a manifest.
The manifest can be part of a PE file, or it can be a standalone in an assembly with more than one file. Although this is not an exhaustive list, a manifest contains:
Files in the assembly
In addition, a developer can set custom attributes for an assembly, such as a title and a description.
An End to DLL Hell?
One of the great benefits of COM was supposed to be an end to DLL hell. If you think back for a moment to the days of 16-bit programming, you'll remember that you had to distribute a number of DLLs with a Windows application. It seemed that almost every application had to install the same few DLLs, such as Ctrl3d2.dll. Each application that you installed might have a slightly different version of the DLL, and you ended up with multiple copies of the same DLL, but many were different versions. Even worse, a DLL could be placed in the Windows\System directory that then broke many of your existing applications.
COM was supposed to fix all that. No longer did applications search around for DLLs by looking in their own directories, and then search the Windows path. With COM, requests for components were sent to the registry. Although there might be multiple versions of the same COM DLL on the machine, there would be only one version in the registry at any time. Therefore, all clients would use the same version. This meant, however, that each new version of the DLL had to guarantee compatibility with previous versions. This led to interfaces being immutable under COM; after the component was in production, the interface was never supposed to change. In concept, that sounds great, but developers released COM components that broke binary compatibility; in other words, their components modified, added, or removed properties or methods. The modified components then broke all existing clients. Many VB developers have struggled with this exact problem.
The .NET Framework and the CLR attempt to address this problem through the use of assemblies. Even before .NET, Windows 2000 introduced the capability to have an application look in the local directory for a DLL, instead of going to the registry. This ensured that you always had the correct version of the DLL available to the application.
The runtime carries this further by allowing components to declare dependencies on certain versions of other components. In addition, multiple versions of the same component can be run simultaneously in what Microsoft calls side-by-side instancing or side-by-side execution.
The Global Assembly Cache (GAC)
Even though components in .NET do not have to be registered, there is a similar process if you have an assembly that is to be used by multiple applications. The CLR actually has two caches within its overall code cache: the download cache and the global assembly cache (GAC). An assembly that will be used by more than one application is placed into the global assembly cache by running an installer that places the assembly in the GAC.
The GAC is where you place a component if you want multiple applications to use the same component. This is very similar to what you have with registered COM components in VB6.
Placing assemblies in the GAC has several advantages. Assemblies in the GAC tend to perform better because the runtime locates them faster and the security does not have to be checked each time that the assemblies are loaded. Assemblies can be added to or removed from the GAC only by someone with administrator privileges.
Where things get interesting is that you can actually have different versions of the same assembly in the GAC at the same time. Notice that I avoided saying "registered in the GAC" because you aren't placing anything in the registry. Even if a component is running in the GAC, you can add another version running alongside it, or you can slipstream in an emergency fix. This is all based on the version number, and you have control over whether an update becomes a newly running version or merely an upgrade to an existing version.
Assemblies can be placed in the GAC only if they have a shared name.