Talvolta Γ¨ necessario dover interagire con librerie non gestite (DLL esterne) e accedere a delle loro strutture dati che si trovano nella memoria unmanaged.
Il metodo PtrToStructure<T>
presentato qui fornisce un modo efficiente per ottenere una struct gestita di tipo T
da un puntatore IntPtr
alla memoria non gestita.
Il framework fornisce giΓ il metodo Marshal.PtrToStructure<T>(IntPtr)
ma, nella sua implementazione interna, utilizza object
e conseguentemente porta a del boxing con conseguente allocazione sullo Heap.
Implementazione
Il metodo Γ¨ molto semplice, utilizza il metodo Unsafe.AsRef<T>
per eseguire una conversione efficiente senza causare boxing.
public static unsafe T PtrToStructure<T>(IntPtr ptr) where T : struct
{
return Unsafe.AsRef<T>(ptr.ToPointer());
}
Esempio
Supponiamo di avere una libreria non gestita che fornisce una struct
, ad esempio:
// C code (Unmanaged library)
typedef struct Point {
int x;
int y;
} Point;
Per utilizzare questa struct
in unβapplicazione C# gestita, definiremo una struct
corrispondente e useremo il metodo PtrToStructure<T>
per convertire il puntatore alla memoria non gestita in un oggetto gestito:
// C# code (Managed application)
// voglio che i campi vengano disposti in memoria nello stesso ordine in cui sono dichiarati nella struct, quindi prima X, poi Y
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int X;
public int Y;
}
public static class NativeMethods
{
[DllImport("unmanaged_library.dll")]
public static extern IntPtr GetUnmanagedPoint();
}
public class Program
{
public static void Main()
{
IntPtr unmanagedPtr = NativeMethods.GetUnmanagedPoint();
Point managedPoint = PtrToStructure<Point>(unmanagedPtr);
Console.WriteLine($"X: {managedPoint.X}, Y: {managedPoint.Y}");
}
}
Lβimportanza di StructLayout Sequential
Lβattributo StructLayout(LayoutKind.Sequential)
viene utilizzato per controllare la disposizione dei membri di una struct
in memoria, in particolare garantisce che i campi della struct
vengano disposti in memoria nello stesso ordine in cui vengono dichiarati.
Il compilatore non puΓ² quindi riorganizzare i campi per ottimizzare lβallocazione di memoria o per altri motivi.
Questo Γ¨ particolarmente importante quando si lavora con codice non gestito poichΓ© Γ¨ necessario garantire che la struct
gestita corrisponda esattamente alla struct
non gestita in termini di layout in memoria.
PotenzialitΓ
- Prestazioni migliorate: Il metodo
PtrToStructure<T>
offre un vantaggio in termini di prestazioni rispetto aMarshal.PtrToStructure<T>(IntPtr)
poichΓ© evita il boxing e conseguentemente non alloca nulla sullo heap. - FlessibilitΓ : Funziona con qualsiasi tipo di struct, rendendolo flessibile e adattabile a diverse situazioni.
Limiti
- Sicurezza: Γ importante assicurarsi di utilizzare questo metodo solo con puntatori validi e con
struct
correttamente definite; essendo un metodoUnsafe
ho meno controllo sui casi di errore. Devo essere certo che lβIntPtr
in questione punti effettivamente al tipoT
da me richiesto. - CompatibilitΓ : Lβutilizzo di
Unsafe.AsRef<T>
potrebbe non essere supportato su tutte le piattaforme o in tutti i contesti.
AccessViolationException
Dato che il metodo opera a basso livello utilizzando funzionalitΓ βunsafeβ devo pensare allβeventualitΓ di una AccessViolationException
.
Questa eccezione viene generata quando si tenta di leggere o scrivere memoria protetta o non valida. PuΓ² verificarsi se il puntatore IntPtr
fornito al metodo PtrToStructure<T>
non Γ¨ valido o se punta a una zona di memoria protetta.
Esempio: Se il puntatore IntPtr
restituito dalla funzione non gestita punta a una zona di memoria non accessibile, chiamando PtrToStructure<T>
si otterrΓ unβeccezione AccessViolationException
.
IntPtr invalidPtr = new IntPtr(0x12345678); // Puntatore non valido
Point managedPoint = PtrToStructure<Point>(invalidPtr); // AccessViolationException