9 Stimmen

TypeLoadException auf x64, aber problemlos auf x86 mit Strukturlayouts

Sie benötigen einen 64bit-Rechner, wenn Sie die tatsächliche Ausnahme sehen wollen. Ich habe einige Dummy-Klassen erstellt, die das Problem repro's.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
    public class InnerType
    {
        char make;
        char model;
        UInt16 series;
    }

 [StructLayout(LayoutKind.Explicit)]
    public class OutterType
    {
        [FieldOffset(0)]
        char blah;

        [FieldOffset(1)]
        char blah2;

        [FieldOffset(2)]
        UInt16 blah3;

        [FieldOffset(4)]
        InnerType details;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var t = new OutterType();
            Console.ReadLine();
        }
    }

Wenn ich dies auf dem 64er-Clr ausführe, erhalte ich eine Type Load Exception,

System.TypeLoadException was unhandled 
  Message="Could not load type 'Sample.OutterType' from assembly 'Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field."

Wenn ich die Ziel-CPU auf 32 zwinge, funktioniert es gut.

Auch, wenn ich InnerType von einer Klasse zu einer Struktur ändern es funktioniert auch. Kann jemand erklären, was los ist oder was ich falsch mache?

danke

0 Stimmen

Beachten Sie, dass char in .NET 16-Bit ist. blah und blah2 überschneiden sich teilweise, ebenso wie blah2 und blah3.

21voto

Stephen Martin Punkte 9256

Der Teil über überlappende Typen ist hier irreführend. Das Problem ist, dass in .Net Referenztypen immer an Zeigergrößengrenzen ausgerichtet werden müssen. Ihre Vereinigung funktioniert in x86, da das Feld Offset ist 4 Byte, die die Zeigergröße für ein 32-Bit-System ist, aber nicht auf x64, da es ein Vielfaches von 8 versetzt werden muss. Das gleiche würde passieren, wenn Sie den Offset auf 3 oder 5 auf der x86-Plattform setzen.

EDIT: Für die Zweifler - ich konnte keine fertige Referenz im Internet finden, aber schauen Sie mal unter Expert .NET 2.0 IL Assembler von Serge Lidin Seite 175.

0 Stimmen

Tolle Antwort. Klar, prägnant. Perfekt.

3voto

Brian Punkte 116

Mir ist auch aufgefallen, dass Sie Ihren Datentyp char in 1 Byte packen. Char-Typen in .NET haben eine Größe von 2 Byte. Ich kann nicht überprüfen, ob dies das eigentliche Problem ist, aber ich würde das doppelt überprüfen.

0voto

ShuggyCoUk Punkte 35230

Wenn Sie structs innerhalb anderer structs platzieren wollen, die ihrerseits Layoutind.Explict sind, sollten Sie einen expliziten Size-Wert (in Bytes) verwenden, wenn Sie erwarten, dass sie in verschiedenen Bitness-Modi funktionieren (oder auf Maschinen mit unterschiedlichen Packing-Anforderungen) Was Sie damit sagen wollen, ist: "Legen Sie die Dinge sequentiell an und packen Sie nicht intern, sondern verwenden Sie am Ende so viel Platz wie Sie wollen". Wenn Sie die Größe nicht angeben, kann die Laufzeitumgebung so viel Platz hinzufügen, wie sie will.

Der Grund für die generelle Weigerung, Strukturen und Objekttypen überlappen zu lassen, ist, dass die GC-Routine die Freiheit haben muss, den lebenden Objektgraphen zu durchlaufen. Dabei kann sie nicht wissen, ob ein vereinigtes (überlappendes) Feld als Objektreferenz oder als rohe Bits (z.B. ein int oder ein float) sinnvoll ist. Da sie debe alle lebenden Objektreferenzen durchlaufen, um sich korrekt zu verhalten, würde es damit enden, dass "zufällige" Bits durchlaufen werden, die irgendwo im Heap (oder aus ihm heraus) zeigen könnten, als ob sie Referenzen wären, und bevor man sich versieht, hat man einen General Protection Fault.

Da 32/64-Referenzen je nach Laufzeit 32 oder 64 Bits belegen, müssen Sie Explict verwenden, nur Referenzen mit Referenzen und Werttypen mit Werttypen vereinigen, sicherstellen, dass Ihre Referenztypen an den Grenzen der beiden Zielplattformen ausgerichtet sind, falls sie sich unterscheiden (Hinweis: Laufzeitabhängig, siehe unten), und eine der folgenden Maßnahmen ergreifen:

  1. Stellen Sie sicher, dass alle Referenzfelder der letzte Eintrag in der Struktur sind - es steht dann frei, die Struktur je nach der Bitdichte der Laufzeitumgebung zu vergrößern oder zu verkleinern.
  2. Erzwingen Sie, dass alle Objektreferenzen 64 Bit verbrauchen, unabhängig davon, ob Sie in einer 32- oder 64-Bit-Umgebung arbeiten.

Hinweis zur Ausrichtung: Entschuldigung Ich habe mich bei den nicht ausgerichteten Referenzfeldern geirrt - der Compiler hat die Typladung entfernt, es sei denn, ich habe irgendeine Aktion mit der Struktur durchgeführt.

[StructLayout(LayoutKind.Explicit)]
public struct Foo
{
    [FieldOffset(0)]
    public byte padding;
    [FieldOffset(1)]
    public string InvalidReference;
}

public static void RunSnippet()
{
    Foo foo;
    foo.padding = 0;
    foo.ValidReference = "blah";
    // Console.WriteLine(foo); // uncomment this to fail
}

Die entsprechenden Details finden Sie in der ECMA-Spezifikation http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf siehe Abschnitt 16.6.2, in dem die Ausrichtung von Werten nativer Größe einschließlich & vorgeschrieben ist. Es wird darauf hingewiesen, dass die Anweisung "unaligned prefix" existiert, um dies bei Bedarf zu umgehen.

Auf mono jedoch (sowohl OSX intel als auch Win32 intel 32 bit) funktioniert der obige Code. Entweder respektiert die Laufzeitumgebung die Layouts nicht und "korrigiert" die Dinge stillschweigend, oder sie erlaubt eine willkürliche Ausrichtung (in der Vergangenheit war sie in dieser Hinsicht weniger flexibel als die MS-Laufzeitumgebung, was überraschend ist). Die von mono erzeugte CLI-Zwischenform enthält keine .unaligned-Befehlspräfixe und scheint daher nicht mit der Spezifikation übereinzustimmen.

Das wird mich lehren, nur noch bei Mono nachzusehen.

0 Stimmen

Wenn Sie Ihr RunSnippet-Beispiel ausführen, erhalten Sie die gleiche TypeLoadException wie oben.

0voto

Ich kämpfte mit dem gleichen Problem und hasste es, dass ich auf MSDN keine klare Referenz zu diesem Thema finden konnte. Nachdem ich die Antwort hier gelesen hatte, begann ich, mich auf die Unterschiede zwischen x86 und x64 in .NET zu konzentrieren und fand Folgendes: Migrieren von verwaltetem 32-Bit-Code zu 64-Bit . Hier heißt es eindeutig, dass Zeiger bei x86 4 Byte und bei x64 8 Byte groß sind. Ich hoffe, es kann für andere hilfreich sein.

Es gibt übrigens viele ähnliche Fragen hier auf Stack Overflow. Ich werde zwei von ihnen hinzufügen, die andere interessante Dinge erwähnen.

-2voto

Sebastian Punkte 821

Vielleicht gibt es etwas Falsch mit der Uint16 aufgrund es ist nicht CLS-konform (siehe hier: http://msdn.microsoft.com/en-us/library/system.uint16.aspx )

0 Stimmen

Wenn ich die uint16 entferne und die Offsets dekrementiere, tritt das Problem immer noch auf.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X