Introduzione al
CLR/MSIL
Alfredo Paolillo e Marco Servetto
Vocabolario
IL:
Intermediate Language, Standard ECMA del 1997
MSIL:
Microsoft IL, Implementazione Microsoft di IL
Introduzione
Perché .NET
Ambiente di esecuzione
Common Language Runtime
Perché .NET
Difficile effettuare sviluppo omogeneo
Molto tempo viene dedicato a far comunicare i
vari “strati”
Serve un salto qualitativo per semplificare lo
scenario
Codici
Evoluzione
Codice nativo
Codice interpretato
Codice MSIL
Codice nativo
Sorgenti
Compilatore
Codice
nativo
(.EXE)
Output
Codice interpretato
Sorgenti
Interprete
Output
Codice MSIL
Sorgenti
Compilatore
.NET
Codice
MSIL
(Assembly)
.EXE/.DLL
Compilatore
JIT
Codice
nativo
Output
Codice MSIL
Sorgenti
Compilatore
.NET
Codice
MSIL
(Assembly)
.EXE/.DLL
Codice +
metadati
Compilatore
JIT
Codice
nativo
Output
Codice MSIL
Sorgenti
Compilatore
JIT
Ambiente di
Compilatore
esecuzione
.NET
.NET Runtime
Codice
nativo
Codice
MSIL
(Assembly)
.EXE/.DLL
Output
Motori JIT
Inizialmente previsti 4 motori:
Motore
Descrizione
Dove si trova
Attuale
implementazione
JIT
OptiJit
Codice più ottimizzato
Non implementato
FastJit
Esecuzione JIT più veloce
.NET Compact
Framework
Native
(Pre-Jit)
Compilazione preventiva, assembly
compilato salvato in GAC
NGEN.EXE
JIT – Just in Time Compiler
In teoria, come con Java, è possibile compilare
MSIL ed eseguirlo (interpretato) in qualsiasi
ambiente che supporti l’esecuzione
La compilazione di un’applicazione da un tipo di
codice assembly quale MSIL verso un codice
eseguibile sulla macchina nativa dovrebbe
appesantire le prestazioni dell’applicazione
È quello che succede?
JIT – Just in Time Compiler
Il codice non viene caricato tutto in memoria
il compilatore JIT compila solo il codice
necessario, quindi memorizza nella cache il
codice nativo compilato per riutilizzarlo
L’overhead è una lieve differenza che, nella
maggior parte dei casi, non verrà rilevata
JIT – Just in Time Compiler
Quando viene caricata una classe, il caricatore
aggiunge uno stub a ogni metodo della classe
La prima volta che viene chiamato il metodo, il
codice stub cede il controllo al compilatore JIT,
che compila MSIL nel codice nativo.
Lo stub viene quindi modificato per puntare al
codice nativo appena creato, affinché le chiamate
successive passino direttamente al codice nativo
Indipendenza dalla piattaforma
.NET è un’implementazione di CLI
CLI è uno standard ECMA
Common Language Infrastructure
ECMA-334, ECMA-335
Esistono già altre implementazioni di CLI:
SSCLI (Microsoft, per Windows, FreeBSD e
Macintosh)
Mono (per Linux)
DotGNU
Intel OCL (Open CLI Library)
…
Codice IL
Tutto questo assomiglia a qualcosa di già visto?
Forse Java?
Ci sono delle differenze
Un compilatore Java crea bytecode, che in fase
di esecuzione viene interpretato tramite JVM
.NET crea un codice nativo
Codice IL
Un vantaggio rilevante offerto da .NET
Framework rispetto a Java e JVM è la scelta del
linguaggio di programmazione
JVM solo Java
.NET Multilinguaggio (VB.net, C#, J# etc…)
Vediamo un esempio di IL
Assembly
Modulo
(file PE)
Codice IL
Metadati
Manifest
Assembly
Metadati
Concetto chiave in .NET
Informazioni sui tipi di un assembly
Generati automaticamente dai compilatori
Estendibili da terze parti
Formato binario rappresentabile con XML:
XML Schema (XSD)
Serializzazione e deserializzazione
oggetti a runtime in XML
Metadati
Descrizione di un assembly
Descrizione dei tipi
Identità: nome, versione, cultura [, pubblic key]
Tipi esportati
Assembly da cui dipende
Nome, visibilità, classe base, interfacce implementate
Attributi custom
Definiti dall’utente
Definiti dal compilatore
Codice IL
Proviamo adesso a scrivere e compilare dei
semplici programmi in C# e proviamo ad
analizzarli
Codice IL
Esempio 1
namespace testUno
{
public class esempioUno
{
public esempioUno()
{
}
static void Main(string[] args)
{
int primaVariabile = 0x1234;
int secondaVariabile = 0xabcdef;
}
}
}
Codice IL
Il file eseguibile è costituito da due parti:
la prima è il codice MSIL, utilizzato per generare il
codice nativo
la seconda è rappresentata dai metadati
Con un tool in dotazione con l’SDK possiamo
Diassemblare il file ottenuto dalla compilazione
Otterremo il seguente output
Tralasceremo comunque alcuni dettagli come il codice
del costruttore di classe
Codice IL
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size
13 (0xd)
.maxstack 1
.locals init (int32 V_0, int32 V_1)
IL_0000: ldc.i4 0x1234
IL_0005: stloc.0
IL_0006: ldc.i4 0xabcdef
IL_000b: stloc.1
IL_000c: ret
} // end of method esempioUno::Main
Codice IL – istruzioni principali
.entrypoint
Specifies that this method is the entry point to the application (only one
such method is allowed).
.maxstack
int32 specifies the maximum number of elements on the evaluation stack
during the execution of the method
.locals [init]
Defines a set of local variables for this method.
ldc.i4:
Description Push num of type int32 onto the stack as int32.
stloc.0:
Description: Pop value from stack into local variable 0.
ret:
Description: return from method, possibly returning a value
Codice IL – Metainformazioni
ScopeName : testUno.exe
MVID : {F01C8E38-E942-43D9-9D71-95D37789D357}
===========================================================
Global functions
------------------------------------------------------Global fields
------------------------------------------------------Global MemberRefs
------------------------------------------------------TypeDef #1
------------------------------------------------------TypDefName: testUno.esempioUno (02000002)
Flags : [Public] [AutoLayout] [Class] [AnsiClass] (00100001)
Extends : 01000001 [TypeRef] System.Object
Method #1
------------------------------------------------------MethodName: .ctor (06000001)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA
: 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #2 [ENTRYPOINT]
------------------------------------------------------MethodName: Main (06000002)
Flags : [Private] [Static] [HideBySig] [ReuseSlot] (00000091)
RVA
: 0x00002064
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: SZArray String
1 Parameters
(1) ParamToken : (08000001) Name : args flags: [none] (00000000)
Codice IL – Metainformazioni
TypeRef #1 (01000001)
------------------------------------------------------Token:
0x01000001
ResolutionScope: 0x23000001
TypeRefName:
System.Object
MemberRef #1
------------------------------------------------------Member: (0a000002) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
TypeRef #2 (01000002)
------------------------------------------------------Token:
0x01000002
ResolutionScope: 0x23000001
TypeRefName:
System.Diagnostics.DebuggableAttribute
MemberRef #1
------------------------------------------------------Member: (0a000001) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
2 Arguments
Argument #1: Boolean
Argument #2: Boolean
Codice IL – Metainformazioni
Signature #1 (0x11000001)
------------------------------------------------------CallCnvntn: [LOCALSIG]
2 Arguments
Argument #1: I4
Argument #2: I4
Assembly
------------------------------------------------------Token: 0x20000001
Name : testUno
Public Key :
Hash Algorithm : 0x00008004
Major Version: 0x00000000
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
Flags : [SideBySideCompatible] (00000000)
CustomAttribute #1 (0c000001)
------------------------------------------------------CustomAttribute Type: 0a000001
CustomAttributeName: System.Diagnostics.DebuggableAttribute :: instance void .ctor(bool,bool)
Length: 6
Value : 01 00 00 01 00 00
>
<
ctor args: ( <can not decode> )
Codice IL – Metainformazioni
AssemblyRef #1
------------------------------------------------------Token: 0x23000001
Public Key or Token: b7 7a 5c 56 19 34 e0 89
Name: mscorlib
Major Version: 0x00000001
Minor Version: 0x00000000
Build Number: 0x00001388
Revision Number: 0x00000000
Locale: <null>
HashValue Blob:
Flags: [none] (00000000)
Codice IL
I metadati vengono organizzati in tabelle, in cui
fondamentalmente viene descritto ciò che il
codice definisce e a cui fa riferimento
Prestiamo attenzione a questa parte di codice:
CallCnvntn: [LOCALSIG]
2 Arguments
Argument #1: I4
Argument #2: I4
Codice C#
Proviamo adesso a compilare il seguente codice
FILE:esempioDueB
namespace testDue
{
public class esempioDueB
{
static void Main(string[] args)
{
esempioDueA variabile = new esempioDueA();
variabile.printString();
}
}
}
Codice C#
FILE: esempioDueA
using System;
namespace testDue
{
public class esempioDueA
{
public esempioDueA()
{
}
public void printString()
{
string s = "Hello!!!!";
Console.Write(s);
}
}
}
Codice IL
Disassembliamo:
A differenza di prima dovremo analizzare due
codici
Codice IL
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size
13 (0xd)
.maxstack 1
.locals init (class testDue.esempioDueA V_0)
IL_0000: newobj instance void testDue.esempioDueA::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: callvirt instance void testDue.esempioDueA::printString()
IL_000c: ret
} // end of method esempioDueB::Main
Codice IL
.method public hidebysig instance void printString() cil managed
{
// Code size
13 (0xd)
.maxstack 1
.locals init (string V_0)
IL_0000: ldstr "Hello!!!!"
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call
void [mscorlib]System.Console::Write(string)
IL_000c: ret
} // end of method esempioDueA::printString
Codice IL
Principali differenze rispetto al codice precedente:
Newobj:
Assembli format: newobjctor
Description: allocate an uninitialized object or value type and call ctor
Call:
Assembli format: call method
Description: Call method described by method
Callvirt:
Assembli format: callvirt method
Description: Call a method associated with obj
Codice IL
Andiamo nuovamente a riesaminare le meta-informazioni:
Signature #2 (0x11000002) (EsempioDueB)
------------------------------------------------------CallCnvntn: [LOCALSIG]
1 Arguments
Argument #1: Class testDue.esempioDueA
Signature #1 (0x11000001) (EsempioDueA)
------------------------------------------------------CallCnvntn: [LOCALSIG]
1 Arguments
Argument #1: String
Codice IL – Metainformazioni
Method #2 (definizione del metodo invocato dalla call)
------------------------------------------------------MethodName: printString (06000002)
Flags : [Public] [HideBySig] [ReuseSlot] (00000086)
RVA
: 0x00002064
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
..................
User Strings (costante)
------------------------------------------------------70000001 : ( 9) L"Hello!!!!”
Codice C#
Passaggio di parametri:
namespace testTre
{
public class esempioTreA
{
static void Main(string[] args)
{
string s = ("HELLO!!!!!!!!!!!!!!!!!!!!!!!!!!");
esempioTreB variabile = new esempioTreB();
variabile.printString(s);
}
}
}
Esempio C#
Using system;
public class esempioTreB
{
public esempioTreB()
{
}
public void printString(string s)
{
Console.Write(s);
}
}
Codice IL
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size
20 (0x14)
.maxstack 2
.locals init (string V_0, class testTre.esempioTreB V_1)
IL_0000: ldstr "HELLO!!!!!!!!!!!!!!!!!!!!!!!!!!"
IL_0005: stloc.0
IL_0006: newobj instance void testTre.esempioTreB::.ctor()
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: ldloc.0
IL_000e: callvirt instance void testTre.esempioTreB::printString(string)
IL_0013: ret
} // end of method esempioTreA::Main
Codice IL
.method public hidebysig instance void printString(string s) cil managed
{
// Code size
7 (0x7)
.maxstack 1
IL_0000: ldarg.1
IL_0001: call
void [mscorlib]System.Console::Write(string)
IL_0006: ret
} // end of method esempioTreB::printString
Codice IL
ldarg.1
Assembli format: ldarg.1
Description: Load argument 1 onto stack
Esistono anche delle varianti, ad esempio:
ldarg num
Assembli format: ldarg num
Description: Load argument numbered num onto stack.
Codice IL – Metainformazioni
TypeDef #1
------------------------------------------------------TypDefName: testTre.esempioTreB (02000002)
Flags : [Public] [AutoLayout] [Class] [AnsiClass] (00100001)
Extends : 01000001 [TypeRef] System.Object
Method #1
------------------------------------------------------MethodName: .ctor (06000001)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA
: 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #2
------------------------------------------------------MethodName: printString (06000002)
Flags : [Public] [HideBySig] [ReuseSlot] (00000086)
RVA
: 0x00002064
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
1 Arguments
Argument #1: String
1 Parameters
(1) ParamToken : (08000001) Name : s flags: [none] (00000000)
Codice IL – Metainformazioni
Signature #1 (0x11000001)
------------------------------------------------------CallCnvntn: [LOCALSIG]
2 Arguments
Argument #1: String
Argument #2: Class testTre.esempioTreB
Assenza di Signature #2
La classe su cui viene invocato il metodo printString non ha dichiarazioni locali
Garbage Collector
Gli oggetti vengono distrutti automaticamente
quando non sono più referenziati
Algoritmo Mark-and-Compact
Garbage Collector - fase 1: Mark
NextObjPtr
Root set
Oggetti “vivi”
Oggetti non raggiungibili
Spazio libero
Garbage Collector - fase 2:
Compact
Spazio recuperato
NextObjPtr
Root set
Oggetti “vivi”
Spazio libero
GC e distruzione deterministica
In alcuni casi serve un comportamento di
finalizzazione deterministica:
Riferimenti a oggetti non gestiti
Utilizzo di risorse che devono essere rilasciate
appena termina il loro utilizzo
Non si possono usare i finalizzatori, che non
sono richiamabili direttamente
Implementare l’interfaccia IDisposable
Common Type System
Tutto è un oggetto
Due categorie di tipi:
Tipi reference (riferimento)
Tipi value (valore)
Allocati su heap gestito
Allocati su stack o in oggetti gestiti (reference)
Tutti i tipi value possono essere visti come tipi
reference
Boxing
Tipi value e reference in memoria
public struct Size {
public int height;
public int weight;
}
public class CSize {
public int height;
public int weight;
}
void Main() {
Size
v;
//
v.height = 100; //
CSize
r;
//
r.height = 100; //
r = new CSize(); //
r.height = 100; //
}
v.height
v.width
v istanza di Size
ok
r è un reference
illegale, r non assegnato
r fa riferimento a un CSize
ok, r inizializzata
height
width
r
Stack
Heap
Class CSize
Equivalenza e identità
Il confronto tra oggetti può essere:
di equivalenza
Object.Equals: oggetti con stesso tipo e uguale contenuto
di identità
Object.ReferenceEquals: stessa istanza o entrambi null
==: dipende dal tipo (come ReferenceEquals o altro)
Object.GetHashCode: rappresentazione univoca istanza
r1
r2=r1;
height
width
r2
Stack
Heap
Class CSize
Equivalenza e identità
“Teo”
19
a
b
c
“Ugo”
d
38
“Ugo”
38
.Equals(d)
==d
a
false
false
b
true
false
c
true
true
Boxing
I tipi value si possono sottoporre a “boxing” per
supportare le funzionalità tipiche degli oggetti
Un tipo value “boxed” è un clone indipendente
Un tipo value “boxed” può tornare
ad essere value (unboxing)
System.Object è il tipo universale
Boxing
Stack
i
int i = 123;
object o = i;
int k = (int)o;
Heap
123
int i = 123;
o
int
123
object o = i;
k
Boxing
123
int j = (int)o;
Unboxing
Conclusioni
Evoluzione della macchina virtuale
Si cerca di trovare il miglior compromesso tra
sicurezza, flessibilità e prestazioni
Non tutto è documentato
Scarsa documentazione per quanto riguarda i
metadati
Altre Informazioni
Dove posso ottenere maggiori informazioni
www.microsoft.com/msdn/italy/studenti
www.ugidotnet.org
www.gotdotnet.com
www.ecma-international.org
Developer resources
Microsoft Visual Studio.NET
Microsoft .NET Framework SDK
Microsoft Developer Network