2014-01-30 8 views
6

Ich habe in einem seltsamen Szenario Marshalling Gewerkschaften, die Arrays in C# /. NET enthalten. Betrachten Sie das folgende Programm:Marshal Union mit Arrays

namespace Marshal 
{ 
    class Program 
    { 
     [StructLayout(LayoutKind.Sequential, Pack = 1)] 
     struct InnerType 
     { 
      byte Foo; 
      //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)] 
      //byte[] Bar; 
     } 


     [StructLayout(LayoutKind.Explicit, Pack = 1)] 
     struct UnionType 
     { 
      [FieldOffset(0)] 
      InnerType UnionMember1; 

      [FieldOffset(0)] 
      [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)] 
      byte[] UnionMember2; 
     } 

     static void Main(string[] args) 
     { 
      Console.WriteLine(@"SizeOf UnionType: {0}", System.Runtime.InteropServices.Marshal.SizeOf(typeof(UnionType))); 
     } 
    } 
} 

Wenn Sie dieses Programm ausführen, erhalten Sie die folgende Ausnahme erhalten:

Could not load type 'UnionType' from assembly 'Marshal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field. 

Nun, wenn Sie die beiden kommentierten aus Linien Kommentar-, läuft das Programm gut. Ich frage mich, warum das so ist. Warum beseitigt das Hinzufügen eines zusätzlichen Arrays zu InnerType das Problem? Übrigens spielt es keine Rolle, wie groß das Array ist. Ohne das Array sollten UnionMember1 und UnionMember2 in der Größe übereinstimmen. Mit dem Array stimmen ihre Größen nicht überein, dennoch werden keine Ausnahmen ausgelöst.

aktualisieren Ändern InnerType auf folgende Ursachen auch die Ausnahme (auf InnerType diesmal):

[StructLayout(LayoutKind.Explicit, Pack = 1)] 
struct InnerType 
{ 
    [FieldOffset(0)] 
    byte Foo; 

    [FieldOffset(1)] 
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)] 
    byte[] Bar; 
} 

Es scheint mir, dass dies auf den ursprünglichen Code äquivalent sein sollte (mit LayoutKind.Sequential), wo byte[] Bar ist unkommentiert.

Ich glaube nicht, dass das Problem hier etwas mit Wortgrenzen zu tun hat - ich benutze Pack = 1. Eher denke ich, es ist der zweite Teil der Ausnahme, "... es enthält ein Objektfeld bei offset 0, das ... durch ein Nicht-Objekt-Feld überlappt. " Byte [] ist ein Referenztyp, während das Byte selbst ein Werttyp ist. Ich kann sehen, dass "Byte Foo" am Ende "byte [] UnionMember2" überlappt. Dies erklärt jedoch immer noch nicht, warum das Auskommentieren von "byte [] bar" in meinem ursprünglichen Code die Ausnahme aufhebt.

+0

Diese Beiträge sollten helfen: http://stackoverflow.com/questions/1190079/incorrectly-aligned-or-overlapped-by-a-non-object-field-error, http://stackoverflow.com/questions/ 4673099/union-in-c-sharp-falsch-ausgerichtet-oder-überlappt-mit-einem-Nicht-Objekt-Feld. Wenn Sie den Feld-Offset für das UnionMember2 auf 8 setzen, wird dies behoben. –

+0

@David Venegoni - Danke! Ich habe diese aber schon angeschaut. # 1190079 ist spezifisch für den CF-Marshaller. Ich benutze keine CF und ich benutze Pack. # 4673099 hat eine Lösung für das Problem dieses Benutzers, erklärt aber nicht genau, was die Ausnahme verursacht. Wenn Sie einen Offset von 8 verwenden, wird die Ausnahme (und die Union) zwar gelöscht, es werden jedoch keine Hinweise darauf gegeben, warum das Hinzufügen von InnerType.bar die Ausnahme unterdrückt. – watkipet

+0

Der Pinvoke-Marshaller lässt Sie keine Struktur zu einem Byte [] zuordnen, das ist keine sinnvolle Konvertierung. Eine Größe von 1 ist auch bizarr, das ist nur ein einfaches Byte. Sie dürfen auch keinen Referenztyp wie Byte [] mit einem Werttyp überlappen. Der Garbage Collector kann nicht mehr genau sehen, ob das Feld eine Referenz speichert. Um dies zu beheben, muss das Feld stattdessen als Puffer fester Größe deklariert werden. –

Antwort

0

Meine Hypothese ist, dass das sequenzielle Layout wie in dieser SO-Antwort beschrieben überschrieben wird: LayoutKind.Sequential not followed when substruct has LayoutKind.Explicit.

Beachten Sie, dass die Packeinstellung von 1 Padding nicht eliminiert, dh Bar hat kein FieldOffset von 1. Es ist auf 4 Bytes ausgerichtet (auf x32), und nach Marshal.OffsetOf() sollte es wie erwartet 4 sein.

Allerdings könnte die .NET-Laufzeit tatsächlich legte den Referenztyp Bar vor dem Byte Foo in verwalteten Speicher, in welchem ​​Fall es richtig in der Vereinigung mit UnionMember2 überlappen würden.

Das interessante ist, dass mit Foo int und float das gleiche passiert, aber mit langen und doppelten gibt es wieder die Ausnahme. Es sieht so aus, als ob es die Felder nach Größe sortiert, aber Referenztypen zuerst einfügt, wenn die Größe gleich ist.

Als ich zu x64 wechselte dann arbeitete es auch mit long Foo, die diese Theorie unterstützen würde. Schließlich habe ich ein Speicherfenster (Debug-> Windows-> Memory) geöffnet und den Speicherort &instance.UnionMember1.Foo eingegeben und ein wenig nach oben geblättert, um die Bytes vor Foo zu enthüllen. Setzen Sie dann einen Wert in Foo und Bar die sofortige Fenster verwendet, die bewiesen Bar bei 0 und Foo ist bei 4 ° C (Add var instance = new UnionType()-Main)

halten Bitte beachten Sie jedoch, dass dies wahrscheinlich nicht das, was Sie beabsichtigt, die Byte[] werden nur als Referenztyp betrachtet. Sie könnten es durch object ersetzen. Je nach Ihren Zielen können Sie stattdessen fixed byte Bar[1] verwenden.