ho i seguenti tipi di valore .NET:layout di .NET tipo di valore in memoria
[StructLayout(LayoutKind.Sequential)]
public struct Date
{
public UInt16 V;
}
[StructLayout(LayoutKind.Sequential)]
public struct StringPair
{
public String A;
public String B;
public String C;
public Date D;
public double V;
}
Ho codice che sta passando un puntatore a un tipo di valore di codice non gestito, insieme con offset scoperti chiamando sistema .Runtime.InteropServices.Marshal.OffsetOf. Il codice non gestito sta compilando la data e i valori doppi.
Gli offset che vengono segnalati per la struct StringPair sono esattamente quello che mi aspettavo: 0, 8, 16, 24, 32
ho il seguente codice in una funzione di test:
FieldInfo[] fields = typeof(StringPair).GetFields(BindingFlags.Instance|BindingFlags.Public);
for (int i = 0; i < fields.Length; i++)
{
int offset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(StringPair), fields[i].Name).ToInt32();
Console.WriteLine(String.Format(" >> field {0} @ offset {1}", fields[i].Name, offset));
}
Che stampa esattamente queste compensazioni.
>> field A @ offset 0
>> field B @ offset 8
>> field C @ offset 16
>> field D @ offset 24
>> field V @ offset 32
Poi ho un po 'di codice di prova: foreach (coppia StringPair in coppia) { Data d = pair.D; double v = pair.V; ...
che ha la seguente assemblatore associato nel debugger:
Date d = pair.D;
0000035d lea rax,[rbp+20h]
00000361 add rax,20h
00000367 mov ax,word ptr [rax]
0000036a mov word ptr [rbp+000000A8h],ax
00000371 movzx eax,word ptr [rbp+000000A8h]
00000378 mov word ptr [rbp+48h],ax
double v = pair.V;
0000037c movsd xmm0,mmword ptr [rbp+38h]
00000381 movsd mmword ptr [rbp+50h],xmm0
Si sta caricando il campo D all'offset 32 (0x20) e il campo V all'offset 24 (0x38-0x20). Il JIT ha cambiato l'ordine. Il debugger di Visual Studio mostra anche questo ordine invertito.
Perché !? Mi sono tirato fuori i capelli, provo a vedere dove la mia logica sta andando male. Se scambio l'ordine di D e V nella struct, allora tutto funziona, ma questo codice deve essere in grado di gestire un'architettura di plugin in cui altri sviluppatori hanno definito la struttura e non ci si può aspettare che ricordi regole di layout arcane.
Grazie - Mi ero perso il fatto che i metodi Marshall. * Si applicavano solo al puntatore del marshalling. Per motivi di prestazioni, spero di evitare una copia extra dei dati, ma sembra piuttosto inevitabile se voglio supportare le strutture arbitrarie –
+1 per chiarire che il layout della struttura JIT è un problema completamente separato rispetto a quello specificato dal CLR, poiché-- in linea di principio - non può essere visto da - e quindi non può importare ai programmi gestiti. Ho notato che JITer sembra posizionare i campi gestiti prima non gestiti. –
Il layout esatto dei bit in memoria di (un'istanza di) un tipo di valore può avere una certa rilevanza per decidere se implementare IEquatable (e le sostituzioni raccomandate di object.Equals e GetHashCode). Se non implementi IEquatable, allora credo che il codice generato di default esegua il confronto bit a bit sui bit in memoria. Se ci sono GAPS nel layout, si può perdere tempo a confrontare i bit che non contano (in C++, si potrebbe anche rischiare la correttezza dato che quei bit potrebbero non essere inizializzati - ma penso che siano sempre in C#). Conoscere il layout informa la decisione su cosa implementare. –