.Net stravolge la classica visone di programmare, introducendo un nuovo
modo di realizzare e distribuire software, basato su standard quali HTML e
XML, per rendere, finalmente, l’interoperabilità una realtà. Elemento
fondamentale di .Net è il .Net framework, una piattaforma per la creazione e
l’esecuzione di applicazioni, che, sebbene non sia indispensabile, facilita
molto il lavoro. I vantaggi derivanti dall’utilizzo di .Net framework, sono
diversi, ma quelli decisamente più interessanti, sono rappresentati dal fatto
che usando il framework, è possibile trasferire sul web il concetto di
programmazione orientata agli oggetti, e che fornisce un API comune a tutti i
linguaggi di programmazione.
1.1 L’INIZIATIVA DI MICROSOFT
Come già detto con l’introduzione di .Net Microsoft vuole rivoluzionare i
concetti di realizzazione e di distribuzione del software; questo perché vuole
rendere Internet una piattaforma software orientata ai servizi. Ciò significa
che l’idea è quella di offrire agli utenti internet una serie di servizi web XML.
Per servizio web XML si intende un’applicazione eseguita da un web server
che permetta agli utenti connessi di eseguire metodi web o di usare funzioni
API; il termine XML sta ad indicare che tali applicazioni si basano sul
linguaggio XML per permettere lo scambio dei dati. In questo modo Internet
diventerà la piattaforma software con un’API più potente di qualsiasi sistema
operativo, il che farà si che le future applicazioni non si baseranno più
sull’API del sistema operativo, ma sui servizi web.
E’, a questo punto, opportuno specificare che i servizi web di cui stiamo
parlando non sono stati inventati da Microsoft, né sono una sua esclusiva, al
contrario, si basano su standard aperti (HTML, XML, SOAP…), e stanno già
proliferando su internet (la maggior parte di quelli esistenti non è stata
sviluppata usando .Net).
A questo punto è lecito chiedersi quale sia il contributo di .Net al mondo dei
servizi web, se è possibile crearne senza avvalersi di tale strumento.
Il contributo di Microsoft .Net al mondo dei servizi web, è rappresentato da:
- In primo luogo dall’esperienza di un gigante del settore come
MICROSOFT.
- In secondo luogo, Microsoft è impegnata a sviluppare una serie di suoi
servizi web (.Net My Services) che permettono di aggiornare i prodotti
server delle imprese.
- In terzo luogo, forse il più importante, .Net offre la possibilità di
sviluppare servizi web usando .Net Framework, il che semplifica
enormemente il lavoro .
A questo punto iniziamo ad osservare più da vicino questo potente strumento
messoci a disposizione da Microsoft, per creare i nostri servizi web.
1.2 MICROSOFT .NET FRAMEWORK
Prima di cominciare a scoprire .Net Framework, è opportuno ricordare che è
una piattaforma per la realizzazione e l’esecuzione del software, ed in
particolare di web services; e che il suo utilizzo non è necessario, ma è di
fondamentale importanza, perché facilita enormemente il compito del
programmatore.
.Net Framework è costituito di due elementi fondamentali, CLR o Common
Language Runtime, e FCL o Framework Class Library; sono questi due
elementi a rendere così fondamentale l’uso di questa piattaforma. Per poter
spiegare cosa è CLR, è necessario introdurre il concetto di applicazione
gestita, si tratta di un’applicazione le cui azioni sono subordinate
all’approvazione di un modulo di gestione. CLR è il modulo di gestione di .Net
Framework. Per quanto riguarda FCL, fornisce l’ API (orientata agli oggetti)
alle applicazioni gestite; come abbiamo detto in precedenza, uno dei
problemi principali della interoperabilità tra diverse applicazioni è dovuto
all’utilizzo di diverse API, in questo modo, tramite FCL, .NET Framework
mette a disposizione delle diverse applicazioni la stessa API.
Sebbene il motivo principale che spinge un programmatore ad utilizzare .Net
Framework è quello di creare web services, questo non è l’unico fine per il
quale può essere utilizzato, infatti, con esso è possibile realizzare applicazioni
console, applicazioni GUI (windows Forms), applicazioni web (web Forms).
Fino ad ora abbiamo detto che grazie a .Net Framework è possibile realizzare
diversi tipi di applicazioni, tuttavia è stato omesso uno degli aspetti più
interessanti, infatti, parte del framework è ASP.NET, nuova versione di ASP
(Active Server Pages) che ha introdotto un nuovo modo di realizzare
applicazioni web; e altrettanto promette di fare ASP.NET.
Andiamo ora ad analizzare più da vicino le due componenti fondamentali del
.Net Framework.
1.2.1 COMMON LENGUAGE RUNTIME
CLR rappresenta il cuore di .Net Framework, qualsiasi porzione di codice
scritta per il framework, viene eseguita nell’ambiente CLR, o al di fuori, ma
con autorizzazione di CLR; in altre parole tutto ciò che avviene in .Net
Framework, coinvolge, più o meno direttamente CLR.
Abbiamo detto che CLR è un modulo di gestione, per questo resta al di sopra
del sistema operativo, rappresenta un ambiente virtuale nel quale vengono
accolte le applicazioni gestite. Il codice con il quale vengono scritte tali
applicazioni, viene chiamato Common Intermediate Language o CIL, le
istruzioni di questo linguaggio, normalmente non vengono scritte dal
programmatore, ma vengono compilate in Just in Time (JIT) durante la fase
di esecuzione. Proprio per la scelta del JIT, per evitare carichi di lavoro, il
codice viene compilato una volta sola, e quindi posto nella cache, dalla quale
viene prelevato ad ogni sua invocazione.
Nonostante la scelta della politica JIT per la compilazione sia onerosa dal
punto di vista prestazionale, l’idea di compilare ciascuna porzione di codice
una sola volta, fa sì che l’impatto del just in time sulle prestazioni, sia
ridotto. Inoltre si è fatto in modo di rendere il compilatore JIT il più
performante possibile, così che il codice compilato con JIT possa superare
nelle prestazioni il codice ordinario; questo perché JIT ottimizza il codice
nativo che genera, per il particolare processo host sul quale è in esecuzione.
Il codice compilato da JIT non ha nulla a che vedere con il codice
interpretato.
A questo punto è lecito chiedersi quali siano i vantaggi derivanti dall’utilizzo
di un modulo di gestione come CLR.
La decisione di ricondurre tutte le istruzioni al linguaggio CIL, rende le
applicazioni totalmente indipendenti dal linguaggio di programmazione, che
dal punto di vista di .Net non è altro che uno strumento sintattico per
produrre istruzioni CIL. Grazie a questa funzionalità è possibile creare delle
classi in un dato linguaggio, e utilizzarle in un altro.
Un indiscutibile vantaggio è quello che hanno i neofiti, nel senso che CLR
controlla il codice prima di eseguirlo, il che impedisce, ai programmatori
meno esperti, di fare danni. Proprio per questa verifica del codice, CLR
impedisce la compilazione di codice che vuole danneggiare intenzionalmente
l’host sul quale è in esecuzione.
Sembrerebbe che tutte queste verifiche limitino enormemente la libertà del
programmatore, che tuttavia può decidere di disattivare tale protezione,
interpellando l’amministratore di sistema.
DOMINI APPLICAZIONI
Grazie alla verifica del codice, CLR riesce a far coesistere all’interno dello
stesso processo, più applicazioni, al contrario di windows che invece isola le
singole applicazioni, sprecando una grande quantità di memoria. CLR è in
grado di attuare tutto ciò, decomponendo il singolo processo in
compartimenti virtuali denominati Domini Applicazioni, un ottimo uso di tali
domini applicativi può essere quello in applicazioni web, dove la gestione
della memoria diventa di fondamentale importanza per gestire molti utenti
contemporaneamente.
Riassumendo, CLR, divide ciascun processo in diversi domini applicazioni,
ciascuno dei quali è in grado di ospitare un’applicazione; tali domini
applicazioni sono validi anche da un punto di vista della sicurezza, perché
creano dei limiti che le applicazioni gestite non possono oltrepassare. Un
altro ottimo uso dei domini applicazioni è quello di caricare le librerie, che in
tal modo possono essere condivise da tutti i domini del processo.
GARBAGE COLLECTION
Quello di poter eseguire la Garbage Collection, è un altro vantaggio derivante
dall’eseguire il codice in ambiente gestito. Per capire meglio i vantaggi
derivanti da questa possibilità, ricordiamo cosa si intende per Garbage
Collection:
La gestione della memoria non è più a carico del programmatore, ma del
modulo di gestione, CLR, tramite il Garbage Collector, che libera memoria,
distruggendo ciò che contiene, solo nel momento in cui viene richiesta da
altre applicazioni. E’ chiaro che anche questa caratteristica offre al contempo
vantaggi e svantaggi.
Vantaggi:
- In questo modo, le applicazioni costituite di solo codice gestito non
perdono memoria.
- La procedura di Garbage Collection migliora le prestazioni, grazie alla
velocità dell’algoritmo di allocazione della memoria di CLR
Svantaggi:
- Durante l’esecuzione della procedura di Garbage Collection, tutti gli
altri processi attivi, vengono momentaneamente sospesi.
MODULI GESTITI
Introduciamo ora il concetto di modulo gestito, poiché esso viene creato ogni
volta che si genera del codice. Chiaramente ciò è indipendente dal
linguaggio. Con il termine modulo gestito, si intende indicare un eseguibile
atto ad essere processato in ambiente CLR. Ogni modulo gestito è costituito
da vari componenti:
- Un’intestazione di file PE (portable executable) di windows.
- Un’intestazione CLR, molto importante perché tiene traccia della
posizione di elementi essenziali del modulo gestito, come le istruzioni
CIL, e i metadati
- I metadati, il cui compito è quello di descrivere i vari elementi del
modulo e le rispettive dipendenze esterne
- Le istruzioni CIL generate dal codice sorgente
Tra questi elementi i metadati rivestono un ruolo di fondamentale
importanza, poiché sono utilizzati da CLR per determinare i tipi di dati
presenti all’interno del modulo, proprio perché rivestono un ruolo così
importante che i metadati non sono facoltativi, ma ogni modulo deve avere i
suoi. Tuttavia questo non è il solo modo di sfruttare i metadati, infatti, grazie
a questi in Visual Studio .Net è possibile, nel momento in cui si inserisce il
nome di un’istanza di classe, osservare tutti i metodi e le proprietà della data
classe.
E’ proprio perché sono così importanti che i metadati vengono visti con
grande attenzione; vengono memorizzati in diverse tabelle, ognuna con un
diverso ruolo. Nella tabella TypeDef viene tenuta traccia di tutti i tipi
contenuti nel modulo, in un’altra tabella sono elencati tutti i metodi del
modulo, non meno importanti sono le tabelle che tengono traccia dei
riferimenti esterni, o meglio i riferimenti a tipi esterni.
Non tutti i metadati sono mantenuti in tabelle, alcuni, infatti, sono
mantenuti negli heap, che vengono utilizzati dalle voci delle tabelle come dei
riferimenti (es: i nomi delle classi e dei metodi vengono memorizzati
nell’heap di stringa).
Abbiamo detto che una parte essenziale di un modulo gestito è quella
contenente le istruzioni CIL, tale parte è disseminata di token di metadati
che si riferiscono a voci delle tabelle dei metadati (ciò è possibile perché ogni
voce di tali tabelle è identificata da un token di 32 bit).
Ogni volta che il compilatore crea le istruzioni CIL di un metodo, crea con
esse un token di metadati che identifica la riga che contiene le informazioni
sul metodo.
E’ grazie a tali token che CLR è in grado di risalire a tutte le informazioni sui
metodi. Per ciò che riguarda la modifica dei metadati, di norma questa non
avviene in modo diretto, ma è affidata a CLR e ai compilatori.
Per la grande importanza che rivestono, .Net Framework mette a
disposizione uno strumento per esaminare i metadati: ILDASM.
COMMON INTERMEDIATE LANGUAGE
Abbiamo detto che tutto il codice creato da compilatore, indipendentemente
dal linguaggio utilizzato viene ricondotto ad istruzioni in linguaggio CIL, che
può essere inteso come uno pseudo assembly, in quanto definisce una serie
di istruzioni native per un processore, in questo caso, però, il processore non
sarà una cpu fisica, ma CLR. Abbiamo già detto che l’opera di traduzione in
CIL, non è a carico del programmatore, che pertanto potrebbe
tranquillamente ignorare la sintassi di CIL. Tuttavia, è possibile che i metodi
appartenenti alla FCL (Framework Common Library) diano errori inaspettati,
e per questo sarebbe opportuno avere una buona familiarità con CIL.
CIL ha un set di circa 100 istruzioni, che vanno da quelle più classiche,
tipiche dell’assembly (es:ADD), ad istruzioni più complesse, grazie alle quali
è possibile ridurre codice scritto in c# o in VB.Net a poche righe di istruzioni.
Il modello di esecuzione di cui si avvale CIL è basato su stack. La
dichiarazione di variabili avviene grazie ai metadati. Il compilatore dichiara
nei metadati del particolare metodo, il numero e il tipo di variabili di cui
necessita, queste informazioni vengono recuperate da CLR, che, prima
dell’esecuzione del metodo, alloca la memoria necessaria alle variabili.
Abbiamo già incontrato ILDASM, strumento che ci permette di visualizzare i
metadati, le sue funzionalità non si limitano a ciò, infatti, grazie a questo
strumento è possibile disassemblare istruzioni CIL. In realtà ILDASM è un
disassemblatore a doppio senso, il che significa che è possibile passargli il
codice disassemblato per ottenere il set di istruzioni CIL dal quale è stato
ottenuto.
A questo punto possiamo affermare che, sebbene CIL sia fondamentale a
.Net per ottenere una concreta indipendenza dai linguaggi di
programmazione, comporta la necessità al programmatore di conoscere le
sue istruzioni, per riuscire a far fronte agli errori inattesi cui possono
incorrere i metodi della FCL. Proprio per aiutare i programmatori in tale
compito, è stato inserito in .Net Framework un documento che descrive in
dettaglio tutte le istruzioni CIL.
Un ulteriore problema comportato da CIL, è quello della sicurezza, infatti,
abbiamo visto quanto sia facile disassemblare e riassemblare codice. Tale
problema potrebbe essere trascurabile nel caso di servizi web, nel qual caso
le applicazioni , residenti su server web, non sarebbero visibili agli utenti; ma
per quanto riguarda altri generi di applicazioni l’unico modo per cercare di
evitare di far disassemblare il proprio codice, è quello di usare programmi di
utilità per occultare il codice. E’ chiaro che in nessuno di questi due casi, la
soluzione è definitiva, in quanto, nel primo caso aprendo un varco nel firewall
del server, nel secondo sfruttando le proprie abilità, è possibile arrivare a
leggere il codice.
ASSEMBLY
Introducendo il concetto di Assembly, ci troviamo a scoprire un qualcosa di
decisamente interessante, e cioè che CLR non è in grado di gestire
direttamente i moduli gestiti, in quanto l’unità fondamentale per la verifica
della sicurezza (cioè per il controllo delle versioni) è, appunto, l’Assembly.
Un assembly è un’unità logica costituita da uno o più file, di norma, in questo
contesto, per file si intende un modulo gestito. Un assembly può contenere
anche files che non sono moduli gestiti.
Un possibile utilizzo di assembly con più files, è quello di unire moduli
realizzati in diversi linguaggi, o quello di ottenere moduli gestiti combinando
files comuni e immagini, o per suddividere le applicazioni in un numero
discreto di unità da scaricare da internet.
A questo punto viene automatico chiedersi, come faccia CLR a capire che un
file appartiene ad un dato Assembly, la risposta a questa domanda sottolinea
nuovamente l’importanza dei metadati, infatti, uno dei file contiene un
manifesto, che è, fisicamente, un elemento dei metadati (ogni volta che il
compilatore scrive un modulo gestito che risulta essere anche un assembly,
va a scrivere il manifesto nei metadati del modulo); dal punto di vista logico
il manifesto è il manuale dell’assembly, un documento che spiega il suo
contenuto.
Figura 1 Assembly con più files
A questo punto è utile andare a vedere quali sono gli elementi più importanti
del manifesto:
- Il nome dell’assembly
- Un elenco delle files dell’assembly
- Un numero di versione
Quando un compilatore produce un assembly, a meno di indicazioni
specifiche, crea un assembly con nome non sicuro, il che significa che per
riferirsi ad esso CLR utilizza il nome indicato nel manifesto, e non una firma
crittografata. Tuttavia questo non è il solo tipo di assembly esistente, infatti
si possono avere anche quelli con nome sicuro, il che significa che,
nell’assembly è contenuta una chiave pubblica dell’editore ed una firma
digitale. E’ la firma digitale, generata con la chiave privata dell’editore, e
verificabile con quella pubblica, a rendere l’assembly a prova di intrusione. A
questo punto è normale chiedersi quale tipo di assembly conviene utilizzare,
se quelli a “nome non sicuro”, o quelli a “nome sicuro”; è chiaro che la scelta
è influenzata del tipo di uso cui sono destinato gli assembly. Se l’assembly
deve essere distribuito in modo tale da essere condiviso da più applicazioni,
allora è opportuno che sia con “nome sicuro”; dovrà essere con nome sicuro
anche nel caso in cui si voglia sfruttare la possibilità di effettuare un controllo
sulle versioni (quando viene caricato un assembly, CLR confronta il numero
di versione dell’assembly, con quello utilizzato per compilare l’applicazione
che effettua il caricamento dell’assembly stesso). La scelta di effettuare o
meno la verifica della versione, è una scelta dello sviluppatore, può costituire
un vantaggio o uno svantaggio, infatti, sebbene limita la possibilità di errori
(pensiamo al caso in cui venga, erroneamente, sostituito un assembly
corretto con uno pieno di errori, il controllo della versione ci avvertirebbe),
possono esserci delle circostanze in cui sarebbe meglio che la verifica non ci
fosse, come ad esempio nel caso in cui si voglia sostituire un assembly
difettoso con uno nuovo, riveduto e corretto; in quest’ultimo caso per
permettere di utilizzare la nuova versione di assembly è necessario
rigenerare l’applicazione.
1.2.2 FRAMEWORK CLASS LIBRARY
Abbiamo accennato poc’anzi a FCL come uno dei maggiori artefici
dell’indipendenza dai linguaggi, questo perché grazie a .Net framework i
programmatori avranno a disposizione la stessa API (FCL) indipendentemente
dal linguaggio di programmazione scelto. Tuttavia ogni medaglia ha il suo
rovescio, destino al quale non si sottrae FCL, infatti se da una parte abbiamo
il vantaggio di dover conoscere una sola API per ogni linguaggio di
programmazione, dall’altra abbiamo lo svantaggio di dover imparare una
nuova API, cosa che può essere assimilabile al dover imparare un nuovo
sistema operativo. Anche in casa Microsoft si sono resi conto di questa
grande difficoltà, per questo hanno cercato di organizzare FCL nel modo più
intuitivo possibile, suddividendo le classi in spazi di nomi gerarchici. FCL
consta di circa 100 spazi, ognuno dei quali raggruppa classi e tipi che hanno
uno scopo comune.
1.3 I TIPI IN .NET
Nei paragrafi precedenti abbiamo solo accennato il concetto di FCL, infatti,
questo è un argomento piuttosto ampio, non fosse altro per l’importanza
della libreria di classi messa a disposizione da .Net Framework. A questo
punto è opportuno fare una precisazione, FCL sta per FRAMEWORK CLASS
LIBRARY, ma il termine CLASS, in questo caso è usato impropriamente. Ciò
che ci viene messo a disposizione da .Net Framework, non sono classi ma
tipi. Andiamo con ordine. Abbiamo detto che FCL è una libreria di tipi, a
questo punto è opportuno spiegare cosa si intende, quando si parla di tipi ci
si riferisce a classi, strutture, interfacce, enumerazioni, e delegati. I vari tipi
vengono raccolti in assembly, e condivisi, in modo da renderli disponibili per
ciascuna applicazione. A questo punto andiamo a vedere quali sono le differenze tra i
vari elementi di FCL.
1.3.1 CLASSI
I programmatori più esperti avranno già incontrato questo termine,
nell’ambiente .Net Framework le classi hanno quasi lo stesso significato che
hanno in linguaggi orientati agli oggetti come il C++, vale a dire che sono
porzioni di codice atte a rappresentare degli oggetti. Lo scopo delle classi è di
rappresentare tipi di dati complessi. Ciò che distingue le classi .net da quelle
dei comuni linguaggi orientati agli oggetti è la possibilità di dotarle oltre che
di variabili e funzioni membro, anche di proprietà ed eventi. Pertanto una
classe in .Net Framework presenta le seguenti caratteristiche:
- Campi, analoghi alle variabili di classe in C++
- Metodi, analoghi alle funzioni di classe in C++
- Proprietà, servono ad esporre i dati come i campi, ma sono
implementate mediante metodi delle funzioni di accesso
- Eventi, servono a definire le modifiche attivabili da una particolare
classe.
In C# le classi definiscono dei tipi di riferimento allocati nell’heap
sottoposto a Garbage Collection, il che implica che sia in C#, sia in
qualunque altro linguaggio di programmazione .net, è del tutto assente la
possibilità di eliminare oggetti, infatti sarà compito del Garbage Collector
eliminare gli oggetti creati.
Abbiamo detto che le classi sono definite come tipi di riferimento allocate
nell’heap, il che vuol dire che si accederà ad esse per mezzo di riferimenti
che non hanno niente a che vedere con i puntatori fondamentali. Ai tipi di
riferimento si contrappongono i tipi di valore, le differenze tra i due tipi
possono tranquillamente essere ignorate, poiché .Net Framework li gestisce
in modo del tutto trasparente al programmatore, tuttavia più avanti
spiegheremo brevemente le differenze tra questi due tipi.
Tutte le classi ereditano un metodo denominato Finalize, che viene invocato
ogni volta che il garbage Collector libera memoria, tuttavia, le classi che
racchiudono risorse non gestite, devono ricorrere all’override di tale metodo,
per liberare tali risorse (per risorse non gestite si intendono quelle che non
vengono rilasciate dal Garbage Collector).
1.3.2 STRUTTURE
Abbiamo detto che le classi sono usate per rappresentare dei tipi di dati
complessi, e che, essendo dei tipi di riferimento, sono allocate nell’heap
gestito dal Garbage Collector, tuttavia, è possibile dover gestire delle classi
piuttosto semplici, che è preferibile non far gestire al Garbage Collector, per
non appesantire la procedura di liberazione della memoria. Queste classi,
pertanto, non verranno allocate nell’heap, ma nello stack. I tipi che vengono
allocati nello stack, vengono detti tipi di valore. Le strutture sono delle classi
definite come tipi di valore ( in C# i tipi di valore vengono definiti grazie alla
parola chiave struct).
A questo punto andiamo ad esaltare alcune delle differenze tra i due tipi. I
tipi di valore si differenziano da quelli di riferimento, perché risiedendo nello
stack, richiedono un minore overhead; inoltre sono soggetti ad alcune
restrizioni, non possono racchiudere delle risorse gestite ( poiché non
essendo gestiti, una volta distrutti non possono rilasciare tali risorse), non
possono derivare da altri tipi.
1.3.3 INTERFACCE
In .Net Framework le interfacce hanno un significato del tutto analogo a
quello visto in Java, sono cioè una collezione di dichiarazioni di metodi, che
possono essere implementati da una classe o struttura. In .Net le interfacce
possono includere oltre a metodi, anche eventi e proprietà, ma va detto che
queste eventualità sono piuttosto rare. Una classe o struttura, per
implementare un’interfaccia, dovrà derivare da essa, e fornire
l’implementazione dei suoi metodi.
1.3.4 ENUMERAZIONI
Anche per questa categoria vale la stessa osservazione fatta per le classi,
infatti, le enumerazioni di .Net Framework, sono del tutto simili a quelle
messe a disposizione da C++. Una enumerazione è un elenco di costanti alle
quali viene attribuito un nome, in C#, tale operazione viene effettuata
usando la parola chiave enum. In .Net Framework è possibile utilizzare le
enumerazione, come se fossero un parametro di metodo, vediamo un
esempio: la stringa di codice che andiamo a riportare di seguito serve ad
analizzare un testo senza distinguere tra minuscole e maiuscole:
Regex regex = new Regex (exp. RegexOptions.IgnoreCase);
In questo caso il membro dell’enumerazione è RegexOptions. Sì è offerta
questa possibilità, perché utilizzare parole chiare, al posto di numeri,
conferisce al codice una leggibilità decisamente superiore.