Utilizzo “sicuro” di stringhe in .NET 2.0

La classe System.String non rappresenta la soluzione più sicura quando è necessario memorizzare sotto forma di stringa contenuti confidenziali, ad esempio stringhe di connessione a database, password varie, numeri di carta di credito, ecc.. Informazioni di questo genere andrebbero mantenute in memoria solo il tempo strettamente necessario alla loro elaborazione, e la memoria occupata andrebbe rilasciata il più presto possibile. Questo accorgimento è necessario poichè il contenuto della memoria utilizzata da un processo in esecuzione potrebbe essere letta attraverso varie tecniche di hacking, e quindi il suo eventuale contenuto confidenziale correrebbe seri pericoli di manomissione. Inoltre, un processo in esecuzione potrebbe utilizzare un file di swap su disco che provocherebbe la scrittura anche di questo genere di informazioni.

[Perchè una stringa è insicura]
La classe System.String non si adatta affatto a queste caratteristiche. Infatti, una stringa è un oggetto immutabile, per cui ogni modifica ad essa comporta la creazione di una nuova stringa memorizzata in un’area di memoria differente, mentre la vecchia stringa continuerà  ad esistere fino alla successiva operazione di garbage collection, che, a causa della sua natura non deterministica, potrebbe avvenire ben più tardi rispetto al momento in cui tutti i riferimenti ad essa sono rilasciati.
L’immutabilità  delle stringhe costituisce un serio problema se una stringa contiene informazioni confidenziali per almeno 3 motivi:
– Ogni manipolazione effettuata su di essa lascia in memoria una copia della stessa, in attesa del garbage collector;
– Non esiste alcun modo per resettare il contenuto della stringa una volta utilizzato; il tentativo di assegnare un valore nullo provocherà  la creazione di un nuovo oggetto stringa con il nuovo contenuto lasciando invariato il vecchio;
– L’indirizzo di memoria in cui è memorizzata una stringa non è costante una volta creata;
– Non è possibile criptare il contenuto di una stringa senza provocare la creazione di un nuovo oggetto con il contenuto criptato;
[La soluzione in .NET 1.0/1.1]
Nella versione 1.0/1.1 del Microsoft .NET Framework non esiste una soluzione semplice a questo problema. Infatti l’unica strada percorribile è quella di criptare/decriptare manualmente il contenuto di una stringa confidenziale utilizzando un array di caratteri come area di lavoro. Non è invece possibile resettare una stringa senza attendere che il garbage collector agisca.

[La soluzione in .NET 2.0]
La versione 2.0 del .NET Framework pone rimedio a tutti i problemi di sicurezza precedentemente elencati attraverso la nuova classe SecureString presente nel namespace System.Security. Questa nuova classe permette di memorizzare in modo sicuro e criptato una stringa contenente contenuto confidenziale, utilizzando DPAPI (Data Protection API), ovvero una serie di API per il criptaggio ed il decriptaggio di informazioni mediante l’algoritmo Triple-DES.

Ecco un esempio di utilizzo:

public SecureString SetSecurePassword(string password)
{
   SecureString ss = new SecureString();
   foreach (char c in password.ToCharArray())
   {
         ss.AppendChar(c);
   }
        
   return ss;
}

Come si può; notare, l’utilizzo di SecureString è un pò più macchinoso rispetto alla creazione di un semplice oggetto stringa; infatti esso richiede che la stringa sicura sia costruita aggiungendo un carattere alla volta attraverso il metodo AppendChar(). Il motivo di questo utilizzo dovrebbe essere ovvio: non è possibile costruire una stringa sicura a partire da una stringa già in memoria in testo in chiaro, poichè quest’ultima sarebbe immutabile con gli identici problemi di sicurezza visti precedentemente. Inoltre, una istanza di SecureString è mutabile, il suo indirizzo di memoria non cambia una volta creata e può quindi facilmente essere azzerata attraverso il metodo Clear() senza attendere il garbage collector. Oltre al metodo AppendChar() esistono altri metodi per modificare il contenuto, ad es. InsertAt(), RemoveAt(), SetAt(), il cui nome è abbastanza intuitivo per comprendere il loro utilizzo. La proprietà MakeReadOnly blocca il contenuto dell’oggetto SecureString non permettendo più alcuna modifica. Questa caratteristica può essere letta attraverso la proprietà  IsReadOnly. Inoltre, la classe SecureString implementa l’interfaccia IDisposable, ovvero permette al suo utilizzatore di distruggere l’istanza in memoria senza attendere il garbage collector.

Dopo aver creato e popolato una istanza della classe SecureString occorre leggere il valore in essa contenuto. Questa operazione non può essere fatta richiamando semplicemente il metodo ToString() della classe, in quanto quest’ultimo non è stato ridefinito e ritorna semplicemente l’oggetto sotto forma di stringa così come ereditato da System.Object, vale a dire System.Security.SecureString, non di grande utilità , e non è semplicissima in quanto comporta una operazione di marshalling (attraverso l’utilizzo della classe Marshal presente nel namespace System.Runtime.InteropServices) che coinvolge l’utilizzo di un puntatore alla stringa, anche se il tutto si risolve con poche righe di codice, come in questo esempio:

 public string GetPassword(SecureString ss)
 {
     IntPtr bstr = Marshal.SecureStringToBSTR(ss);
   
     try
     {
        password = Marshal.PtrToStringUni(bstr);
     }
     finally
     {
         Marshal.ZeroFreeBSTR(bstr);
     }
   
     return password;
}

Una volta ottenuta la controparte immutabile della stringa, è necessario assicurarsi che l’area di memoria puntata dal puntatore sia azzerata attraverso l’utilizzo del metodo ZeroFreeBSTR della classe Marshal.
[Riferimenti]
[1] Maurizio Tammacco – Pinned object in .NET
[1] Maurizio Tammacco – Uso efficiente delle stringhe in .NET

Utilizzare codice unsafe in C#

Il linguaggio C# permette di scrivere codice cosiddetto unsafe, cioè eseguito al di fuori del controllo del CLR (Common Language Runtime).In questo ambito il programmatore può accedere, seppure con le dovute limitazioni rispetto a linguaggi più specifici quali C/C++, direttamente alla memoria attraverso l’uso dei puntatori. Un puntatore è una particolare variabile il cui contenuto è un indirizzo di memoria. In un sistema a 32 bit quindi un puntatore occupa 4 byte.

L’utilizzo diretto della memoria offre vantaggi e svantaggi. Da un lato è possibile superare i controlli imposti dal CLR e dal compilatore C#, e questo permette di scrivere routine altamente ottimizzate e generalmente più efficienti; dall’altro canto però la programmazione unsafe è senz’altro più ostica e complessa della stesura di normale codice gestito, sia per la particolare sintassi a cui bisogna attenersi, sia perchè è facilissimo commettere errori che a questo livello risultano fatali per l’applicazione e per l’intero sistema.

Come utilizzare il codice unsafe

Prima di eseguire codice unsafe è necessario utilizzare l’omonimo modificatore, il quale può essere associato ad una intera classe, ad uno o più metodi, ad una o più variabili membro, oppure ad un singolo blocco di codice all’interno di un metodo.

Nel seguente esempio vengono mostrati tutti i possibili usi:

// Qualsiasi metodo della classe può usare i puntatori
public unsafe class MyClass {}
// I membri (tipi valore) della classe possono essere puntatori, anche con
// diversi livelli di visibilità (private, public, protected)
public class MyClass2 
{
   private unsafe float* pFloat;
   public unsafe int* px2;
   protected unsafe int* px3;

  
// Il metodo può usare i puntatori in qualsiasi punto al suo interno
public unsafe void Method1() {}
 
public void Method2() 
{
   int a=0;
   unsafe
   {
      // E' possibile usare i puntatori all'interno di questo blocco
   }
}

Non è invece possible applicare la parola chiave unsafe ad una variabile privata a livello di metodo. Il seguente codice, infatti, non compilerà :

 public void Method3() 
 {
   // errore di compilazione
   unsafe int* a=0;
}

Tuttavia, pur definendo correttamente un contesto unsafe contenente l’opportuno modificatore, il compilatore C# genererà  comunque un errore di compilazione. Come passo ulteriore, è richiesto il parametro /unsafe da associare alla esecuzione della compilazione (comando csc.exe), oppure, se si utilizza l’ambiente Visual Studio 2003/2005, basta attivare l’opportuno check nella finestra delle proprietà  del progetto.

Un aspetto importantissimo da considerare è che in un contesto unsafe  è possibile utilizzare puntatori solo a tipi valore (tutti i tipi numerici primitivi, datetime e strutture), i quali sono memorizzati all’interno dello stack; non è ammesso quindi l’utilizzo di un puntatore ad un tipo riferimento (tutte le classi del .NET Framework più quelle definite dall’utente che derivano direttamente o indirettamente da System.Object, memorizzate invece nell’heap gestito). Questa limitazione è dovuta al fatto che i tipi riferimento sono appunto memorizzati in una area di memoria chiamata heap costantemente monitorata dal meccanismo del garbage collector e su cui esso agisce per liberare la memoria occupata da oggetti non più referenziati. Risulta evidente che se il programmatore avesse la possibilità  di manipolare l’indirizzo di memoria di un tipo riferimento, il garbage collector non avrebbe più la possibilità di tenerne traccia per poter rilasciare la memoria utilizzata quando il riferimento non è più puntato da nessuna variabile.

Utilizzo dei puntatori in pratica.

Le seguenti righe di codice dichiarano due puntatori rispettivamente ad un intero 32 bit ed ad un float:

int* pintValue;.
float* pfloatValue;

Il seguente snippet code evidenzia invece alcune delle operazioni possibili con i puntatori:

Si dichiarano 2 puntatori ad interi (righe 1 e 2).

Si dichiara una variabile intera assegnandole il valore 20 (riga 3).

Successivamente si assegna l’indirizzo di memoria di intValue al puntatore pintValue attraverso operatore “&” (riga 4). Questo operatore permette appunto di ricavare l’indirizzo di memoria di una variabile e di convertirlo in un puntatore.

Nella riga 5 al puntatore pintValue2 viene assegnato il contenuto del puntatore pintValue.

Successivamente si modifica il valore puntato dal puntatore pintValue2 assegnandogli il valore 50 (riga 6) mediante l’operatore “*”, il quale ha l’effetto opposto rispetto all’operatore “&”, ovvero converte un puntatore verso un tipo di dato a valore.

La riga 7 stampa su console lo spazio di memoria occupato da un intero a 32 bit. Questo valore si ottiene attraverso l’operatore sizeof che accetta un tipo valore come parametro e restituisce la sua occupazione in memoria. Le righe 8 e 10 stampano l’indirizzo ed il valore dei 2 puntatori utilizzati in questo esempio. Per poter stampare l’indirizzo di memoria memorizzato in un puntatore è necessario prima convertire quest’ultimo in un tipo di dato numerico abbastanza ampio da poterlo rappresentare (in questo caso un intero senza segno) attraverso una operazione di cast esplicito.

L’output mostrato a video evidenzia come il primo puntatore (int* pintValue) contenga il valore 50 (il valore assegnato al secondo puntatore int* pintValue2). Questo risultato dimostra l’utilizzo dei puntatori come semplici indirizzi di memoria; infatti, nella riga 6 è stato effettuato un assegnamento dell’indirizzo puntato da pintValue alla variabile pintValue2, e, poichè trattasi di puntatori, ciò che è stato effettivamente assegnato non è il valore di pintValue ma l’indirizzo di memoria. Ne consegue che dopo l’istruzione visualizzata alla riga 6 entrambi i puntatori puntano alla stessa area di memoria per cui qualsiasi modifica al contenuto puntato da uno dei due si riflette anche sull’altro.

Aritmetica dei puntatori

Ad un puntatore è possibile sommare o sottrarre un valore intero. Tuttavia il risultato di questa operazione non è immediatamente intuitivo. Il compilatore, infatti, applica sempre la seguente formula quando esegue una operazione di somma (o sottrazione) su un puntatore:

dove X rappresenta un indirizzo di memoria, ovvero un puntatore, n un valore intero da sommare, T il tipo valore a cui il puntatore si riferisce.

Esempio:

dato un puntatore ad un valore double:

che punta al seguente indirizzo di memoria (valore decimale): 1201550

se sommiamo 1 a tale puntatore attraverso questa istruzione:

il risultato sarà  che il puntatore pDouble conterrà  l’indirizzo di memoria 1201558, ovvero all’indirizzo iniziale sono stati aggiunti 8 byte, vale a dire l’ampiezza in memoria di una variabile di tipo double.

Se avessimo aggiunto il valore intero 3, in questo modo:

l’indirizzo di memoria sarebbe cambiato in 1201574, cioè sarebbero stati aggiunti 24 byte (l’equivalente di 3 valori double) al valore iniziale.

Analogamente, in una operazione di sottrazione i bytes sarebbero sottratti dal valore iniziale.

Da ciò si evince una regola importantissima: se si effettua una operazione di somma o sottrazione su un puntatore di un certo tipo valore, il puntatore risultante punterà  ad una area di memoria contigua in funzione del numero di byte che servono a rappresentare il suddetto tipo. Questa operazione andrebbe effettuata con molta cautela, in quanto non si ha nessuna informazione circa il contenuto dell’area di memoria puntata in seguito alla operazione matematica. Non è affatto garantito, infatti, che essa non contenga alcun dato; al contrario, potrebbe contenere informazioni fondamentali per il corretto funzionamento del processo in esecuzione, ad esempio l’indirizzo di ritorno del metodo corrente. Se queste informazioni venissero sovrascritte da un puntatore sicuramente si otterrebbe un crash del sistema.

Puntatori a strutture

Una struttura è un tipo valore in quanto è memorizzata nello stack esattamente come i tipi numerici primitivi. Quindi è possibile definire un puntatore ad una struttura, ma essa non potrà contenere alcun tipo riferimento, esempio una stringa, in quanto ciò provocherebbe, come già menzionato, un errato funzionamento del garbage collector.

Quindi, disponendo di una struttura come da esempio:

public struct Article
{
   public int code;
   public float price;
   public short foo;
}

è possibile utilizzare i puntatori in questo modo:

(Riga 1) E’ dichiarato un puntatore ad una struttura contenente un valore intero a 32 bit, un float ed un intero a 16 bit (short).

(Riga 2) E’ stampato l’indirizzo a cui punta il puntatore alla struttura e l’ampiezza in memoria della stessa. Quest’ultimo valore è pari a 12 byte, e non coincide con la somma delle ampiezze dei singoli campi della struttura. Infatti il tipo Int32 occupa 4 byte, il tipo float occupa 4 byte ed il tipo short 2, per un totale di 10 byte, e tuttavia lo spazio allocato per la struttura è pari a 12 byte. Questo comportamento è normale su un processore a 32 bit dove la memoria è suddivisa in blocchi da 4 byte poichè tale processore lavora in modo più efficiente quando opera su blocchi di memoria di 4 byte, meglio conosciuto nel sistema Windows come DWORD. Il .NET Framework, quindi, alloca memoria in blocchi di 4 byte anche se la memoria indispensabile per ogni tipo a valore è inferiore.

(Righe 3, 4 5) E’ creata una istanza della struttura Article con l’inizializzazione dei suoi campi

(Riga 6) Il puntatore pArticleStruct punta all’ istanza della struttura Article appena creata

(Righe 7, 8) Attraverso il puntatore pArticleStruct sono letti i valori dei singoli campi della struttura e memorizzati in variabili

(Righe 9, 10) Attraverso il puntatore pArticleStruct sono scritti nuovi valori dei singoli campi della scrittura

(Righe 11, 12) Sono creati puntatori ai singoli campi della struttura Article

(Righe 13, 14) Queste righe sono equivalenti alle righe 12, 13; mostrano quindi una sintassi alternativa per creare dei puntatori ai campi interni di una struttura

(Riga 15) E’ stampato il contenuto dei campi della struttura letti attraverso variabili di appoggio

(Riga 16) E’ stampato il contenuto dei campi della struttura letti attraverso i rispettivi puntatori

(Righe 17, 18, 19, 20) Sono stampati gli indirizzi memorizzati nelle variabili di tipo puntatore

Puntatori a campi di una classe

E’ stato detto che non è possibile creare un puntatore ad un tipo riferimento, almeno utilizzando il linguaggio C#, ma solo ad un tipo valore, in quanto si comprometterebbe il corretto funzionamento del garbage collector. Ad esempio, la compattazione della memoria effettuata dopo una operazione di pulizia non potrebbe più aver luogo se il codice avesse la capacità  di manipolare gli indirizzi di memoria. Tuttavia, una classe può contenere membri di tipo valore, ad esempio un campo pubblico di tipo double. Il compilatore C# permette di definire un puntatore ad un campo di una classe, ma, se si applicasse la stessa logica vista per le strutture, non si avrebbe il risultato desiderato.

Ad esempio, considerando l’esempio Article come una classe:

public class ArticleClass
{
   public int code;
   public float price;
}

compilando il seguente codice:

ArticleClass ArticleObj=new ArticleClass();
ArticleObj.code=5;
ArticleObj.price=50.78f;
int* pIntOfClass=&(ArticleObj.code);
float* pFloatOfClass=&(ArticleObj.price);

si otterrebbe il seguente errore di compilazione:

You can only take the address of an unfixed expression inside of a fixed statement initializer

Questo comportamento è dovuto al meccanismo del garbage collector che potrebbe spostare spostare il riferimento ArticleObj in una zona di memoria diversa da quella in cui è stato creato. Per evitare quindi che a seguito di ciò il riferimento non sia più valido il compilatore C# impedisce di utilizzare puntatori a tipi valore membri di una classe, a meno di non utilizzare la parola chiave fixed che, come facilmente intuibile, obbliga il garbage collector a non spostare il riferimento ad ArticleObj in un’altra zona di memoria perchè è molto probabile che ci siano puntatori ai suoi campi.

Il seguente esempio mostra il suo utilizzo:

 fixed (int* pIntOfClass=&(ArticleObj.code))
 {
   Console.WriteLine("Code: {0}", *pIntOfClass);
 }
   
 fixed (float* pFloatOfClass=&(ArticleObj.price))
 {
   Console.WriteLine("Price: {0}", *pFloatOfClass);
 }

Uso di un array basato sui puntatori

Attraverso l’utilizzo dei puntatori è possibile gestire array ad alte prestazioni basati sullo stack. Un array è rappresentato da una istanza della classe System.Array, e, per tale motivo, è memorizzato nell’heap gestito.

Quindi, se si prova a creare un array di tipi double (cioè un tipo valore) in un metodo, ciò che sarà  memorizzato nello stack è semplicemente il riferimento all’heap gestito che conterrà  i dati veri e propri. Se si ha la necessità  di manipolare un array nello stack in modo da guadagnare in termini di performance è necessario ricorrere ad un puntatore.

Nell’esempio che segue:

double[] doubleArray=new double[20];

E’ dichiarato un array di double di 20 elementi memorizzati nell’heap gestito.

In questo esempio, al contrario, è dichiarato un array di 20 elementi di tipo double memorizzati nello stack:

double* pDoubleArray=stackalloc double[20];

Come si può notare occorre utilizzare la parola chiave stackalloc per la dichiarazione dell’array, seguita dal tipo di dato e dall’ampiezza dell’area di memoria da allocare. Dopo tale dichiarazione la variabile pDoubleArray punterà  al primo elemento dello stesso, quindi la seguente istruzione assegna il valore 10.37 al primo elemento dell’array:

 *pDoubleArray=10.37;

Sfruttando l’aritmetica dei puntatori è possibile accedere ad uno qualsiasi degli elementi di un array in questo modo:

*(pDoubleArray+10)=20.45;

Tuttavia, è possibile utilizzare una sintassi più semplice:

pDoubleArray[10]=20.45;

Il compilatore C# riconosce che la variabile pDoubleArray è un puntatore ad un array di double e permette di accedere all’elemento con l’indice indicato tra parentesi quadre. Quindi le 2 righe di codice viste in precedenza sono equivalenti.

Sfruttando questo meccanismo il seguente blocco di codice crea un array di 20 elementi di tipo intero basato sullo stack, ed assegna ad ogni elemento un numero casuale compreso tra 1 e 100. Successivamente il contenuto dell’array è stampato a video:

Occorre però tenere in considerazione un aspetto importantissimo quando si utilizzano i puntatori per accedere agli elementi di un array. Nell'esempio precedente è stata allocata sufficiente memoria per memorizzare un array di venti interi a 32 bit utilizzando la keyword stackalloc. Se si tentasse di accedere ad un elemento fuori dal range ammesso utilizzando l'array creato in questo modo, ad esempio:

pintArray[50]=10;

non si otterrebbe alcun errore di run-time ma molto probabilmente si comprometterebbe la stabilità  del sistema in quanto l'applicazione avrebbe accesso ad una area di memoria esterna rispetto a quella occupata dall'array di 20 elementi. Se si facesse la stessa operazione su un normale array creato a partire da una istanza di System.Array, il CLR solleverebbe l'eccezione System.IndexOutOfRangeException, preservando in tal modo l'integrità del sistema.

Conclusioni

In questo articolo abbiamo visto come poter accedere direttamente alle memoria utilizzando il linguaggio C#, e come questa operazione possa incrementare le performance di routine in cui la velocità rappresenta un aspetto critico. Analogamente si è visto che questa possibilità  andrebbe usata con molta cautela e solo quando realmente necessario poichè il codice unsafe non è eseguito nel contesto di sicurezza imposto dal CLR, risultando potenzialmente pericoloso per l'integrità  dell'applicazione e dell'intero sistema.

Uso efficiente delle stringhe in .NET

L’architettura Microsoft .NET Framework considera una stringa come un “oggetto immutabile”. Ciò significa che una volta assegnato un valore la stessa non potrà mai più mutare il suo contenuto. Questo comporta che qualsiasi manipolazione effettuata sul contenuto di una stringa una volta che è stata creata produrrà sempre una nuova stringa, ovvero la stringa originaria risulterà raggiungibile dal successivo garbage collector, e una nuova stringa sarà creata e conterrà il risultato della manipolazione. Il seguente pezzo di codice:

C#

string s="SONO UNA STRINGA"; s=s.ToLower(); 

a) crea una stringa; b) la manipola trasformando tutti i caratteri in essa contenuti in minuscolo; c) assegna il risultato alla medesima variabile. La variabile “s” dopo la manipolazione punterà ad un indirizzo diverso rispetto a quello puntato al momento della creazione e dell’inizializzazione del valore. In pratica conterrà un’altro oggetto rispetto a quello originario, appunto perchè l’immutabilità delle stringhe comporta che una volta creato ed inizializzato un oggetto, lo stesso non potrà più mutare per il restante tempo in cui rimarrà in vita. Questa caratteristica presenta vantaggi e svantaggi, ma nel complesso offre un accettabile compromesso per quanto concerne l’efficienza dell’utilizzo della memoria e le prestazioni che ne derivano. Il vantaggio principale dell’immutabilità di una stringa consiste nell’ottimizzazione di operazioni “time-consuming” come la copia, soprattutto nel caso di stringhe grandi. Infatti, poichè è certo che il contenuto di una stringa non muterà mai, una copia del suo contenuto si traduce in una semplice copia dell’indirizzo di memoria a cui essa punta, operazione molto più veloce ed efficiente della copia dell’intero contenuto. Tuttavia, un uso intenso di operazioni di manipolazione di stringhe comporta a lungo andare un degrado delle prestazioni, a causa delle continue operazioni di allocazione e deallocazione di aree di memoria dovuto appunto alla immutabilità della stringhe. Per attenuare questo possibile degrado di prestazioni il .NET Framework mette a disposizione il cosiddetto “intern pool”, ovvero un buffer di memoria in cui è possibile memorizzare stringhe di uso frequente, il cui ciclo di vita coincide con quello dell’intera applicazione. In questo modo le stringhe memorizzate, sono sempre disponibili e non sono mai raggiunte dal garbage collector. Il metodo statico Intern della classe System.String ricerca e memorizza una stringa nel pool in un’ unica operazione. Infatti esso accetta un parametro di tipo stringa che rappresenta la stringa da ricercare; se è presente nel pool il metodo restituisce un riferimento ad essa; se non presente viene aggiunta una nuova stringa e restituito il riferimento, come da esempio:

C#

string s=System.String.Intern("una stringa"); 

Inoltre, operazioni comunissime come la concatenazione producono sempre una stringa temporanea che contiene il risultato della elaborazione che sarà successivamente assegnato alla stringa di risultato. Il seguente semplicissimo codice:

C#

string str="sono una"; string str2=" stringa"; str=str+str2;

Genera una stringa temporanea che conterrà il risultato della concatenazione che sarà assegnato ad un oggetto diverso da quello puntato dalla variabile “str”. Questa operazione di concatenazione, che coinvolge valori non letterali è eseguita a run-time, a differenza della concatenazione di valori letterali eseguita invece durante la compilazione. Per ridurre l’impatto della immutabilità delle stringhe su applicazioni che effettuano parecchie concatenazioni, soprattutto con stringhe abbastanza grandi, è possibile utilizzare l’oggetto StringBuilder, una sorta di buffer preallocato che può essere usato per contenere stringhe concatenate senza la necessità di ulteriori allocazioni di memoria. Una versione di overload del costruttore dell’oggetto StringBuilder accetta un valore intero indicante l’ampiezza della memoria preallocata, il cui valore di default è pari a 16. Fino all’ampiezza massima del buffer preallocato è possibile concatenare stringhe senza ulteriori allocazioni. Oltre questo valore viene allocata nuova memoria, la cui capacità è il doppio dell’ampiezza del vecchio buffer; quest’ultimo è copiato nel nuovo buffer e risulta raggiungibile da una operazione di garbage collection. Infine, le seguenti tecniche possono ridurre la possibilità di scrivere codice inefficiente durante la concetenazione di stringhe: -usare il metodo String.Concat per concatenare una espressione stringa. L’utilizzo di questo metodo non genera una o più stringhe temporanee come invece accade se si usa l’operatore “+”; -usare l’operatore “+” per concatenare stringhe che contengono valori letterali; -usare l’oggetto StringBuilder per concatenare stringhe durante operazioni in cui il numero di concatenazioni non è noto a priori, esempio durante un loop.

Pinned object in .NET

E’ noto che il meccanismo del Garbage Collector del .NET Framework, a cui ho dedicato un articolo apparso sulla rivista Visual Basic Journal, compatta l’ heap dopo aver effettuato la pulizia ed il rilascio della memoria occupata dagli oggetti non più referenziati dall’applicazione. Questa compattazione dispone in modo contiguo gli oggetti ancora in vita per un più efficiente utilizzo della memoria, e chiaramente per tutti gli oggetti spostati vengono aggiornati i rispettivi puntatori a causa del nuovo indirizzo di memoria a cui punta l’oggetto; inoltre, per evitare lunghe operazioni di spostamento, gli oggetti troppo grandi (oltre 85K) non sono spostati. Quindi, a seguito di un GC, un oggetto ancora valido può puntare ad una locazione di memoria diversa rispetto alla locazione che lo stesso oggetto possedeva prima della operazione di GC. Questa situazione comporta problemi se l’oggetto in questione viene passato attraverso il suo indirizzo ad una DLL unmanaged attraverso P/Invoke. Infatti, se il GC cambiasse la locazione di memoria dell’oggetto a seguito di una compattazione dopo la chiamata alla DLL, l’indirizzo passato alla DLL non sarebbe più valido e la DLL non avrebbe alcun modo per accorgersene, con conseguenze facilmente immaginabili. Per ovviare a questo problema è possibile ricorrere alla struttura GCHandle, attraverso cui è possibile creare i cosiddetti oggetti “pinned”, ovvero oggetti non rilocabili in memoria e quindi passabili come parametro a DLL unmanaged. Tuttavia, sono gli oggetti “blittable” possono essere gestiti come pinned (un oggetto blittable è tale se la sua rappresentazione in memoria è la stessa nel codice gestito e nel codice non gestito, es. valori integer, double, ecc; non lo è in caso contrario). Questa struttura permette inoltre di conoscere l’indirizzo in cui un oggetto “pinned” è memorizzato. Se questo valore venisse passato ad un componente unmanaged, si andrebbe incontro al rischio di corruzione della memoria in quanto il componente non gestito avrebbe (teoricamente) la possibilità di scrivere nella memoria puntata dall’indirizzo passato. In questo esempio viene creato un array di interi, reso “pinned” l’oggetto corrispondente e letto il suo indirizzo di memoria.

C#

int[] arrInt = new int[10]; 
arrInt[0]=1; 
arrInt[1]=2; 
arrInt[2]=3; 
System.Runtime.InteropServices.GCHandle GCH = 
System.Runtime.InteropServices.GCHandle.Alloc(
        arrInt, System.Runtime.InteropServices.GCHandleType.Pinned); 
IntPtr a = GCH.AddrOfPinnedObject();

Esecuzione di codice managed come script

La classe System.CodeDom.Compiler.CodeDomProvider è una classe astratta dalla quale derivano classi specifiche utilizzate dal .NET Framework per implementare i compilatori dei vari linguaggi da esso supportati. Il compilatore del linguaggio C#, ad esempio, è basato sulla classe Microsoft.CSharp.CSharpCodeProvider che eredita dalla classe astratta CodeDomProvider e permette di accedere ad una istanza del compilatore C# per la gestione del codice sorgente in detto linguaggio, così come il compilatore del linguaggio Visual Basic .NET è basato sulla classe Microsoft.VisualBasic.VBCodeProvider. Utilizzando tali classi è possibile compilare il codice sorgente, proveniente ad esempio da un file di script, per generare ed eseguire codice managed direttamente in memoria oppure creando un assembly. Ciò si rileva molto utile quando è necessario provare il funzionamento di porzioni di codice sorgente senza dover creare una soluzione da salvare su disco, come ad esempio accade se si utilizza l’ambiente Visual Studio .NET, oppure per fornire ad una applicazione la possibilità di integrazioni software da implementare a livello di scripting. Il primo passaggio consiste nel creare una istanza del compilatore C# oppure VB .NET rispettivamente attraverso la classe CSharpCodeProvider e VBCodeProvider, e di ottenere quindi un oggetto ICodeCompiler che rappresenta una interfaccia la cui implementazione da parte dello specifico compilatore permette di compilare dinamicamente il codice sorgente. E’ opportuno inoltre creare una istanza della classe CompilerParameters per impostare le opzioni di compilazione appropriate.

C#

VB .NET

Alcune di queste opzioni permettono ad esempio di generare un eseguibile oppure una dll, di includere i simboli di debug, di creare un assembly in memoria o di produrre un file su disco (in questo caso è possibile assegnare un nome specifico al file attraverso la proprietà OutputAssembly). Una proprietà importante della classe CompilersParameters è ReferencedAssemblies, ovvero un oggetto di tipo collezione contenente la lista degli assemblies impostati come riferimento dell’assembly in compilazione. Occorre prevedere almeno gli assembly di uso più comune, altrimenti l’utilizzo di una classe senza il riferimento all’assembly che la contiene produce un errore.

C#

VB .NET

A questo punto è possibile leggere il codice sorgente da compilare, tipicamente da un file di testo, e costruire l’entry point dell’assembly insieme al metodo Main che sarà invocato dinamicamente:

C#

VB .NET

Per compilare il codice è necessario invocare il metodo CompileAssemblyFromSource esposto dall'interfaccia ICodeCompiler passandogli l'oggetto CompParameters creato in precedenza e la stringa contenente il codice. Questo metodo restituisce un oggetto CompilerResults che permette di verificare se la compilazione ha prodotto errori o avvertimenti mediante la collection Errors, la quale espone proprietà per individuare la riga e la colonna contenente l'istruzione che ha prodotto l'errore, il numero dell'errore stesso e la sua descrizione, oltre alla proprietà HasErrors che consente immediatamente di verificare se è presente almeno un errore:

C#

VB .NET

Nel caso di compilazione terminata correttamente si effettua un ciclo sui tipi che appartengono all'assembly appena compilato e si ricava un riferimento al tipo "scripter" che costituisce l'entry point dell'assembly e, mediante l'uso di Reflection, si invoca dinamicamente il metodo Main:

C#

VB.NET

Infine, è possibile registrare nel sistema l'applicazione impostando una particolare estensione di file, ad esempio "cssc" per indicare uno script in linguaggio C#, oppure "vbsc" per uno script in Visual Basic .NET. In tal modo attraverso un semplice doppio click sul file con tale estensione sarà eseguito il codice in esso contenuto, in modo simile ad un file di script Visual Basic.

Usare matrici con base diversa da zero in .NET

I linguaggi .NET permettono di definire matrici con un limite inferiore, ovvero l’indice dell’elemento iniziale della matrice stessa, che è sempre uguale a zero. Infatti essi non permettono di gestire array con base personalizzata, ovvero diversa da zero. La seguente istruzione VB .NET dichiara una variabile matrice contenente 11 elementi di tipo stringa, il cui limite inferiore è sempre e solo zero ed il cui limite superiore è 10:

Dim s(10) As String 

Questa caratteristica è ben nota a chi sviluppa in C++/C#, poichè in questi linguaggi un array ha sempre base zero e non è permesso definire basi definite dal programmatore. Al contrario, coloro che hanno scritto programmi utilizzando precedenti versioni del linguaggio Visual Basic, ad esempio Visual Basic 6.0, hanno invece avuto la possibilità di utilizzare matrici con limite inferiore diverso da zero, impostando questo valore nella dichiarazione dell’array. Questa non è affatto una caratteristica del VB6 a cui non si può assolutamente rinunciare, ma è semplicemente una facilitazione per chi sviluppa. Come riportato nel seguente esempio di codice VB6, indicando una matrice di 10 elementi con limite inferiore uguale a 1 e limite superiore uguale a 10, il numero degli elementi della stessa è uguale al suo limite superiore presente nella dichiarazione. Viceversa, nei linguaggi .NET una matrice con limite superiore uguale a 10 contiene in realtà 11 elementi, poichè il limite inferiore parte da zero.

Dim s(1 To 10) As String 

Tuttavia, utilizzare matrici con base personalizzata è una tecnica che potrebbe compromettere l’affidabilità di un’applicazione generando errori anche piuttosto difficili da individuare, soprattutto quando alla stessa ci lavora un team di sviluppo composto da più programmatori. Il CLS (Common Language Specification), ovvero le specifiche tecniche a cui devono obbligatoriamente conformarsi tutti i linguaggi .NET, stabiliscono che una matrice può avere solo un limite inferiore pari a zero, e questo chiaramente per non compromettere l’interoperabilità tra i vari linguaggi. Nonostante le specifiche del CLS, la classe System.Array propria del .NET Framework permette al programmatore di definire un limite inferiore personalizzato per le matrici, e quindi di creare codice non compatibile con il CLS stesso. System.Array costituisce la classe di base utilizzata dai vari linguaggi del .NET Framework per fornire l’implementazione delle matrici utilizzando i costrutti che il linguaggio specifico mette a disposizione. Essa espone metodi shared che consentono di creare un array di elementi e di gestire l’assegnazione dei valori e la lettura degli stessi. Questo approccio richiede la scrittura di codice leggermente più prolisso, ma consente una maggiore flessibilità nell’uso degli array, come appunto creare una matrice con una base diversa da zero. Occorre sempre ricordare però che quest’approccio genera codice non compatibile con le specifiche CLS, e quindi un componente contenente codice di questo tipo potrebbe non essere utilizzato da codice che invece utilizza la sintassi dei linguaggi .NET per creare e gestire gli array. Per ottenere questo risultato è necessario innanzitutto dichiarare una variabile di tipo System.Array, come nel seguente esempio:

Dim Arr As System.Array 

A questo punto è possibile creare una istanza della classe System.Array utilizzando il metodo shared Createnstance. Questo metodo ha una versione di overloading che permette di creare array anche multidimensionali passando 3 parametri, e precisamente un riferimento al tipo di oggetto che dovrà essere contenuto in ogni elemento dell’array, un intero che specifica le dimensioni dell’array da creare, ed ancora un intero che indica il limite inferiore dell’array stesso. Gli ultimi 2 parametri sono array monodimensionali, e ciò significa in pratica che è possibile creare una istanza di un array multidimensionale, ed indicare per ognuna delle sue dimensioni sia la lunghezza che il limite inferiore. Il seguente esempio crea un array di stringhe monodimensionale la cui lunghezza è uguale a 10 ed il cui limite inferiore è uguale a 2:

Dim Lengths as Integer() = {10} 
Dim LowerBounds as Integer() = {2} 
Arr=Array.CreateInstance(GetType(String),Lengths,LowerBounds) 

Dopo aver creato una istanza di un array è possibile popolarlo con dati e in seguito leggerli. Per far questo occorre utilizzare rispettivamente i metodi di istanza SetValue e GetValue della classe System.Array. Il seguente esempio mostra come utilizzarli effettuando un ciclo sull’array appena creato dall’indice zero fino al suo limite superiore. Poichè l’array è stato creato con indice inferiore uguale a 2 e non zero, i primi due cicli generano una eccezione di tipo IndexOutOfRangeException, dimostrando di fatto che il .NET Framework supporta array con base personalizzata, al contrario dei suoi linguaggi di programmazione.

 Dim I as Integer 
 For I = 0 To Arr.GetUpperBound(0) 
 Try 
     Arr.SetValue("Stringa alla posizione " & CStr(I), I) 
     System.Console.WriteLine(Arr.GetValue(I)) 
     Catch ex As IndexOutOfRangeException 
         System.Console.WriteLine(ex.Message) 
End Try 
Next 

Inoltre, se si invoca il metodo System.Array.GetLowerBound(0), il quale ritorna il limite inferiore della dimensione dell’array specificata come parametro, si otterrà il valore 2, che corrisponde al limite inferiore personalizzato impostato come parametro del metodo CreateInstance, come nel seguente frammento di codice:

System.Console.Writeline(Arr.GetLowerBound(0)) 

La classe System.Array è dichiarata utilizzando la parola chiave MustInherit. Questo potrebbe far pensare che è possibile creare una propria classe che eredita da System.Array, e che gestisce funzionalità aggiuntive o ridefinisce i membri della classe base. Sfortunatamente se si tenta di ereditare una propria classe da System.Array si ottiene un errore di compilazione il cui messaggio è “Non è consentito ereditare da System.Array”, poiché solo i linguaggi di programmazione, e quindi i compilatori, possono ereditare da questa classe per fornire i vari costrutti di gestione.

Attributo AllowPartiallyTrustedCallers

In determinate circostanze, è possibile che una applicazione ASP .NET restituisca un errore del tipo “Configuration Error”, “Required permissions cannot be acquired”. Il testo di questo errore è fuorviante, nel senso che non fornisce alcun dettaglio, ma c’è un motivo ben preciso. Esso si verifica quando un membro pubblico di una classe inserita in un assembly firmato con uno strong name e registrato nella GAC, viene chiamato da codice situato in un assembly che non dispone del permesso “FullTrust”. Infatti, come regola generale, per poter accedere ai membri pubblici di classi inseriti in assembly firmati con strong name è necessario che il codice chiamante abbia il permesso FullTrust. Qualsiasi altro permesso genera una SecurityException, mascherata da ASP .NET nello “strano” messaggio di errore menzionato prima, per non fornire dettagli sull’errore che potrebbero risultare pericolosi. Questo requisito è dovuto al fatto che il CLR aggiunge una richiesta “Link Demand” per il permesso FullTrust (quindi solo per il codice immediatamente prima nello stack delle chiamate e non per tutto il codice nello stack) per ogni membro pubblico delle classi situate in assembly firmate con strong name. Questo errore può avvenire anche se l’assembly in questione possiede il permesso “FullTrust” garantitogli dall’amministratore di sistema, ma il suo codice “rifiuta” determinati permessi, utilizzando ad esempio la modalità SecurityAction.RequestRefuse oppure SecurityAction.RequestOptional. In questo caso l’assembly è a tutti gli effetti un “partially trusted assembly”. Il comportamento garantito dal CLR è corretto per la stragrande maggioranza delle situazioni, ma può essere sovrascritto per far fronte a situazioni in cui un assembly “strong named” deve essere richiamato da un assembly “partially trusted”. Per permettere questo, è necessario decorare l’assembly “strong named” con l’attributo AllowPartiallyTrustedCallerAttribute(). Così facendo aumentano i potenziali rischi di sicurezza dell’uso del codice.

Comunque, questa situazione non può essere risolta nel modo citato se la singola classe all’interno di un assembly “strong named” richiede esplicitamente il permesso “FullTrust” per i chiamanti attraverso un LinkDemand oppure un Demand normale. In questo caso, nonostante l’attributo AllowPartiallyTrustedCallers a livello dei assembly, questa classe in particolare continuerà a generare una SecurityException nel caso di codice chiamante che non sia “FullTrust”.