The Common Language Specification sets forth guidelines that a .NET-aware compiler must follow before it can receive support from the Framework. In other words, adhering to the CLS set of rules guarantees that all .NET-targeted languages can achieve interoperability courtesy of Microsoft’s IL code. In order to interact with objects irrespective of their language, objects must expose to callers only features common to all languages. Microsoft has designed the CLS to provide language constructs commonly needed by developers.
Refer to the document that discusses type verification in the Microsoft .NET Framework’s Developer Guide.
Here is a brief description of the verification process: Before running MSIL code, you must convert it using the just-in-time compiler. MSIL is CPU-specific code that runs on the same computer as the JIT compiler. The CLR provides a JIT compiler for each CPU-specific architecture.
The JIT compiles only code required for execution rather than loading all existing MSIL in the PE file. Once it has loaded the code, the JIT stores the native code for subsequent calls. The loader creates a stub and attaches it to each of a type’s methods. With the initial call, the stub relinquishes control to the JIT compiler. The compiler modifies the stub to provide CPU-specific execution instructions. Subsequent calls go directly to the identified native code rather than repeating the process just described. This substantially reduces the time required to run native code.
Another mode of compilation is called install-time code generation. This compilation method converts the MSIL code in the same way as the standard mode just described. However, the JIT converts larger chunks of code at compile time and stores the native code with consideration for what it already knows about other installed assemblies. This file loads and starts more quickly than the standard JIT method.
A verification process validates the code before the JIT compiles the MSIL. It employs reflection to examine both the metadata and MSIL to ensure that the code is type safe. The compiler subsequently accesses only the portions of memory containing the type-safe code. Additionally, the verification process guarantees and enforces security restrictions defined by the CLS.
Let’s examine the verification process a little more closely with an example. It is possible to sign an assembly digitally by using a public/private key pair to construct a shared assembly. At build time, the JIT compiler generates hash code for the assembly and then signs the hash with a private key. Subsequently, it stores the signature in a reserved section of the PE file. The public key is also stored in the assembly. Then, the CLR employs the assembly’s public key to decrypt and verify the assembly’s signature. The original calculated hash is the result of this procedure. Moreover, the CLR applies the information in the manifest to generate the hash dynamically. Next, the hash value is measured against the original hash. If the values are equal, the verification procedure passes and the class loader hands the assembly to the JIT compiler.
The CLR depends on the following items to ensure that it is processing type-safe code:
Identities must be validated.
Only type-safe operations are invoked on an object.
If the JIT receives nonverified code, it generates an exception.