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.
Esempio
Supponiamo di avere una libreria non gestita che fornisce una struct
, ad esempio:
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:
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
.