2009-12-16 6 views
11

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.

risposta

11

Le informazioni che si ottiene dalla classe Marshal è rilevante solo se il tipo viene effettivamente marshalling. Il layout della memoria interna di una struttura gestita non è individuabile tramite mezzi documentati, a parte forse sbirciare il codice assembly.

Ciò significa che CLR è libero di riorganizzare il layout della struttura e ottimizzare l'imballaggio. Lo scambio dei campi D e V rende la struttura più piccola a causa dei requisiti di allineamento di un doppio. Salva 6 byte sul tuo computer a 64 bit.

Non so perché questo sarebbe un problema per voi, non dovrebbe essere. Considerare Marshal.StructureToPtr() per ottenere la struttura strutturata nel modo desiderato.

+1

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

+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. –

+0

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. –

13

Se avete bisogno di layout esplicito ... uso disposizione esplicita ...

[StructLayout(LayoutKind.Explicit)] 
public struct StringPair 
{ 
    [FieldOffset(0)] public String A; 
    [FieldOffset(8)] public String B; 
    [FieldOffset(16)] public String C; 
    [FieldOffset(24)] public Date D; 
    [FieldOffset(32)] public double V; 
} 
+2

avrei giurato che avevo provato che ad un certo punto negli ultimi giorni .. ma sembra funzionare adesso. Tuttavia, il punto è che il * sequenziale * dovrebbe funzionare e il framework sta segnalando offset che non corrispondono a quelli che sta effettivamente utilizzando. Non voglio davvero che gli utenti di questa architettura di plugin debbano specificare un attributo e fare da soli i calcoli matematici per farlo funzionare. –

1

due cose:

  • StructLayout(Sequential) non garantisce l'imballaggio. Potresti voler usare Pack=1, altrimenti le piattaforme a 32 e 64 bit potrebbero essere diverse.

  • e stringa è un riferimento, non un puntatore.Se la lunghezza della stringa è sempre fisso, si potrebbe desiderare di utilizzare array di caratteri fissi:

    public struct MyArray // This code must appear in an unsafe block 
    { 
        public fixed char pathName[128]; 
    }