Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - La classe Marshal e i puntatori

Guida al Visual Basic .NET

Capitolo 91° - La classe Marshal e i puntatori

<< Precedente Prossimo >>

I puntatori

I puntatori sono speciali variabili che non contengono un valore, bensì un indirizzo ad un'altra area di memoria. I puntatori sono una caratteristica peculiare del C e dei suoi immediati discendenti e permettono di gestire la memoria a basso livello. Il .NET non supporta ufficialmente i puntatori, sebbene in C# sia possibile usarli in certi blocchi di codice non gestito. Nonostante ciò, è definito nel namespace System un tipo strutturato di nome IntPtr che rappresenta un puntatore, anche se molto diverso da quelli tipici del C. Nell'ambito di quest'ultimo linguaggio, infatti, si definisce un puntatore specificando a quali tipi di dato esso punti: un puntatore a Integer, significa, ad esempio, che l'area di memoria da esso puntata contiene un numero intero a 32 bit; allo stesso modo, un puntatore a Point indica che l'area di memoria puntata contiene una struttura di quel tipo, che viene rappresentata in binario come sequenza dei suoi membri, ossia due Int32 (X e Y). Questo consente di eseguire operazioni aritmetiche sui puntatori tenendo conto della dimensione occupata dai dati puntati. Non mi dilungo oltre nella spiegazione, pur interessante, poiché in .NET tutto questo non è possibile. Esiste solo IntPtr, che rappresenta un generico puntatore.


La classe Marshal

Il marshalling (da cui il nome della classe) consiste nel passare da dati gestiti a dati non gestiti e viceversa. Tipicamente, i "dati gestiti" sono le entità che noi utilizziamo nella programmazione ad oggetti: tipi value, strutture, oggetti, delegate, eccetera... Parallelamente, i "dati non gestiti" corrispondono alla rappresentazione grezza dei dati in memoria, ossia semplici sequenze di bytes. Per indicare dati non gestiti si utilizzano i puntatori, che riferiscono dove, nella memoria di lavoro, sono state allocate le informazioni che ci servono.
Quando si lavora a basso livello sulla memoria, tuttavia, bisgna ricordarsi di eseguire sempre certe operazioni di cui normalmente non ci preoccupiamo, poiché vengono amministrate dal CLR e dal Garbage Collector:
  • Allocare la memoria prima dell'uso. Nel caso si debba convertire un oggetto nella sua rappresentazione unmanaged, è prima necessario richiedere al gestore della memoria un certo spazio da poter utilizzare per scriverci sopra. Tale spazio deve essere delle dimensioni più piccole possibili, per evitare sprechi;
  • Liberare la memoria dopo l'uso. Quando si è finito di elaborare, tutta la memoria esplicitamente allocata va liberata. In caso ciò non venga effettuato, i dati residui rimarranno ad occupare spazio fino al termine dell'esecuzione. Il Garbage Collector non provvederà al rilascio della memoria, poiché esso opera solo in ambiente managed.
Ecco un semplice esempio:
Imports System.Runtime.InteropServices

Module Module1

    Sub Main()
  'Un nuovo Guid. Il Guid è un tipo di identificativo
  'usato in gran quantità dal Framework.
  'È un tipo strutturato semplice del namespace
  'System, perciò l'ho scelto come esempio
  Dim G As Guid = Guid.NewGuid()
  'Calcola la dimensione in bytes di G
  Dim GuidSize As Int32 = Marshal.SizeOf(G)
  'Un puntatore
  Dim Pointer As IntPtr

  'La funzione AllocHGlobal (dove H sta per Handle) alloca
  'un certo numero di bytes, passato come parametro, nella
  'memoria di lavoro e restituisce un puntatore
  'all'area allocata. In questo caso allochiamo un
  'numero di bytes pari alla dimensione di G:
  Pointer = Marshal.AllocHGlobal(GuidSize)
  'Copia la struttura G nell'area di memoria puntata
  'da Pointer, eventualmente eliminando una vecchia
  'struttura se esiste (True)
  Marshal.StructureToPtr(G, Pointer, True)

  'Ora leggiamo un byte alla volta dall'area di memoria
  'allocata, ed avremo la rappresentazione binaria
  'della variabile G.
  'La funzione ReadByte legge e restituisce un byte di
  'informazione all'indirizzo puntato da Pointer. Come
  'secondo parametro accetta un intero che indica l'offset
  'di cui spostarsi rispetto all'indirizzo base
  For I As Int32 = 0 To GuidSize - 1
Console.Write("{0:X2} ", Marshal.ReadByte(Pointer, I))
  Next

  'Libera la memoria puntata da Pointer. Questa funzione
  'è un po' più sofisticata, poiché
  'non solo libera la memoria strettamente indicata da
  'Pointer, ma elimina anche tutti i sottoriferimenti
  'contenuti nella struttura. Ad esempio, se la struttura
  'contenesse una stringa (che, come sappiamo è un
  'tipo reference), la rappresentazione binaria
  'indicherebbe solo un intero al posto della stringa,
  'ossia un puntatore all'oggetto stringa che risiede
  'in un'altra parte della memoria. DestroyStructure
  'elimina anche riferimenti di questo tipo, assicurando
  'che non rimanga alcuna area di memoria inutilizzata
  Marshal.DestroyStructure(Pointer, GetType(Guid))

  Console.ReadKey()
    End Sub
    
End Module
Gli altri metodi di Marshal hanno un utilizzo altamente specifico e molto tecnico, perciò non è utile analizzarli tutti. I membri più comuni sono stati esposti nell'esempio precedente, ed altre funzioni verrano trattate in seguito parlando di sicurezza.

<< Precedente Prossimo >>
A proposito dell'autore

Programmatore e analista .NET 2005/2008/2010 (in particolare C# e VB.NET), anche nell'implementazione Mono per Linux. Conoscenze approfondite di Pascal, PHP, XML, HTML 4.01/5, CSS 2.1/3, Javascript (e jQuery). Conoscenze buone di C, LUA, GML, Ruby, XNA, AJAX e Assembly 68000. Competenze basilari di C++, SQL, Hlsl, Java.