2010-11-09 5 views
19

Warum funktioniert LayoutKind.Sequential anders, wenn eine Struktur ein DateTime-Feld enthält?Warum funktioniert LayoutKind.Sequential anders, wenn eine Struktur ein DateTime-Feld enthält?

Betrachten Sie die folgenden Code (eine Konsolenanwendung, die mit „unsicher“ aktiviert kompiliert werden müssen):

using System; 
using System.Runtime.InteropServices; 

namespace ConsoleApplication3 
{ 
    static class Program 
    { 
     static void Main() 
     { 
      Inner test = new Inner(); 

      unsafe 
      { 
       Console.WriteLine("Address of struct = " + ((int)&test).ToString("X")); 
       Console.WriteLine("Address of First = " + ((int)&test.First).ToString("X")); 
       Console.WriteLine("Address of NotFirst = " + ((int)&test.NotFirst).ToString("X")); 
      } 
     } 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    public struct Inner 
    { 
     public byte First; 
     public double NotFirst; 
     public DateTime WTF; 
    } 
} 

Nun, wenn ich den Code oben laufen, bekomme ich eine Ausgabe ähnlich den folgenden:

Adresse der Struktur = 40F2CC
Adresse erster = 40F2D4
Adresse NotFirst = 40F2CC

Beachten Sie, dass die Adresse zunächst nicht die gleiche wie die a Adresse der Struktur; Die Adresse von NotFirst ist jedoch das gleiche wie die Adresse der Struktur.

Kommentieren Sie jetzt das Feld "DateTime WTF" in der Struktur und führen Sie es erneut aus. Diesmal bekomme ich eine Ausgabe wie folgt aus:

Adresse von struct = 15F2E0
Adresse erster = 15F2E0
Adresse NotFirst = 15F2E8

Now "First" hat haben die gleiche Adresse wie die Struktur

Ich finde dieses Verhalten überraschend angesichts der Verwendung von LayoutKind.Sequential. Kann jemand eine Erklärung geben? Hat dieses Verhalten irgendwelche Auswirkungen bei Interop mit C/C++ - Strukturen, die den COM DATETIME-Typ verwenden?

[EDIT] HINWEIS: Ich habe festgestellt, dass, wenn Sie Marshal.StructureToPtr verwenden(), um die Struktur zu ordnen, werden die Daten ist in der richtigen Reihenfolge vermarshallten, mit dem „First“ Feld zuerst zu sein. Dies scheint darauf hinzudeuten, dass es mit der Interop funktionieren wird. Das Geheimnis ist, warum sich das interne Layout ändert - aber natürlich wird das interne Layout nie spezifiziert, so dass der Compiler tun kann, was er will.

[EDIT2] "unsafe" von der Strukturdeklaration entfernt (es war übrig von einigen Tests, die ich tat).

[EDIT3] Die ursprüngliche Quelle für diese Frage war von den MSDN C# Foren:

http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/fb84bf1d-d9b3-4e91-823e-988257504b30

+1

Ich denke, Sie haben Ihre eigene Frage beantwortet;) – Doggett

+1

Nun, Gott sei Dank man nie DateTime verwenden, wenn unsicher gehen. :) – leppie

+1

+1 für die Beantwortung Ihrer Frage.Sie sollten eine Antwort mit Ihrer eigenen Antwort erstellen und sie akzeptieren, wenn Sie können. – jgauffin

Antwort

13

Warum funktioniert LayoutKind.Sequential anders, wenn eine Struktur ein DateTime-Feld enthält?

Es ist mit der (surprising) fact that DateTime itself has layout "Auto" (link to SO question by myself) verwandt. Dieser Code reproduziert das Verhalten, das Sie gesehen:

static class Program 
{ 
    static unsafe void Main() 
    { 
     Console.WriteLine("64-bit: {0}", Environment.Is64BitProcess); 
     Console.WriteLine("Layout of OneField: {0}", typeof(OneField).StructLayoutAttribute.Value); 
     Console.WriteLine("Layout of Composite: {0}", typeof(Composite).StructLayoutAttribute.Value); 
     Console.WriteLine("Size of Composite: {0}", sizeof(Composite)); 
     var local = default(Composite); 
     Console.WriteLine("L: {0:X}", (long)(&(local.L))); 
     Console.WriteLine("M: {0:X}", (long)(&(local.M))); 
     Console.WriteLine("N: {0:X}", (long)(&(local.N))); 
    } 
} 

[StructLayout(LayoutKind.Auto)] // also try removing this attribute 
struct OneField 
{ 
    public long X; 
} 

struct Composite // has layout Sequential 
{ 
    public byte L; 
    public double M; 
    public OneField N; 
} 

Beispielausgabe:

 
64-bit: True 
Layout of OneField: Auto 
Layout of Composite: Sequential 
Size of Composite: 24 
L: 48F050 
M: 48F048 
N: 48F058 

Wenn wir das Attribut aus OneField entfernen, die Dinge verhalten sich wie erwartet. Beispiel:

 
64-bit: True 
Layout of OneField: Sequential 
Layout of Composite: Sequential 
Size of Composite: 24 
L: 48F048 
M: 48F050 
N: 48F058 

Diese Beispiele sind mit x64 Plattform Kompilation (so dass die Größe 24, dreimal acht, ist wenig überraschend), sondern auch mit x86 sehen wir die gleichen „ungeordneten“ Zeigeradressen.

Also ich denke ich, dass das Layout OneField abschließen kann (resp. DateTime in Ihrem Beispiel) Einfluss auf die Gestaltung der Struktur hat Sequential ein OneField Mitglied selbst wenn das zusammengesetzte Struktur selbst hat Layout. Ich bin mir nicht sicher, ob dies problematisch (oder sogar erforderlich) ist.


Laut Hans Passanten in dem anderen Thread kommentieren, es nicht mehr macht einen Versuch, es zu halten sequentiellen, wenn eines der Mitglieder ist eine Auto Layout-Struktur.

+2

Endlich eine Antwort auf diese Frage, die eigentlich Sinn macht. – CodesInChaos

2

Ein paar Faktoren

  • Doppelzimmer sind viel schneller, wenn sie ausgerichtet sind
  • CPU-Caches funktionieren möglicherweise besser, wenn keine "Löcher" in der Zündung vorhanden sind

Also der C# -Compiler hat ein paar undokumentierte Regeln verwendet, um zu versuchen, die "besten" Layout von Strukturen, diese Regeln können die Gesamtgröße einer Struktur berücksichtigen, und/oder wenn es eine andere Struktur usw. enthält Wenn Sie das Layout einer Struktur kennen müssen, sollten Sie es selbst angeben, anstatt den Compiler entscheiden zu lassen.

Allerdings das LayoutKind.Sequentiell stoppt den Compiler, indem er die Reihenfolge der Felder ändert.

+1

Sie haben also nur widersprochen? – leppie

+1

@Leppie, nein die Dokumente für LayoutKind.Sequential sagen "... und kann nicht zusammenhängend sein" –

+1

Was ist mit den Feldern Reihenfolge ändern? Überprüfen Sie die Adressen, die hier abgespielt werden, nicht nur ihre Abstände, sondern auch ihre Werte/Reihenfolge. –

3

Zu meiner eigenen Fragen zu beantworten (wie empfohlen):

Frage: „Ist dieses Verhalten keine Auswirkungen haben, wenn Interop mit C/C++ structs, die den Com DATETIME- Typ verwenden zu tun“

Antwort: Nein, weil das Layout beim Marshalling respektiert wird. (Ich verifizierte dies empirisch.)

Frage "Kann jemand eine Erklärung liefern?".

Antwort: Ich bin mir immer noch nicht sicher, aber da die interne Darstellung einer Struktur nicht definiert ist, kann der Compiler tun, was er will.

1

Wenn Sie mit C/C++ interop sind, würde ich immer spezifisch mit dem StructLayout sein. Anstelle von Sequential würde ich mit Explicit gehen und jede Position mit FieldOffset angeben. Fügen Sie außerdem Ihre Pack-Variable hinzu.

[StructLayout(LayoutKind.Explicit, Pack=1, CharSet=CharSet.Unicode)] 
public struct Inner 
{ 
    [FieldOffset(0)] 
    public byte First; 
    [FieldOffset(1)] 
    public double NotFirst; 
    [FieldOffset(9)] 
    public DateTime WTF; 
} 

Es klingt wie Datetime sowieso nicht gemarshallt werden kann, nur in einen String (bingle Marshal Datetime).

Die Variable Pack ist besonders wichtig in C++ - Code, der auf verschiedenen Systemen mit unterschiedlichen Wortgrößen kompiliert werden kann.

Ich würde auch die Adressen ignorieren, die bei der Verwendung von unsicheren Code zu sehen sind. Es spielt keine Rolle, was der Compiler tut, solange das Marshalling korrekt ist.

2

Sie überprüfen die Adressen so, wie sie innerhalb der verwalteten Struktur sind. Marshal-Attribute haben keine Garantie für die Anordnung von Feldern in verwalteten Strukturen.

Der Grund, warum es ordnungsgemäß in systemeigene Strukturen migriert, liegt darin, dass die Daten mithilfe der von Marshal-Werten festgelegten Attribute in den nativen Speicher kopiert werden.

So hat die Anordnung der verwalteten Struktur keinen Einfluss auf die Anordnung der nativen Struktur. Nur die Attribute beeinflussen die Anordnung der nativen Struktur.

Wenn Felder, die mit Marshal-Attributen eingerichtet wurden, in verwalteten Daten auf die gleiche Weise wie native Daten gespeichert wurden, dann hätte Marshal.StructureToPtr keinen Sinn, Sie würden die Daten einfach byteweise kopieren.

+1

Beachten Sie, dass es dem Compiler freisteht, das verwaltete Layout so zu ändern, dass es der vermarshallten Struktur entspricht. Dies ist eine gültige Leistungsoptimierung, die es dem Marshaller ermöglicht, das Kopieren der Struktur in einigen Interop-Szenarien zu vermeiden. Es ist jedoch kein vertragliches Verhalten - Sie sollten niemals irgendwelche Annahmen über das verwaltete Layout von Strukturen machen, unabhängig von 'StructLayout',' FieldOffset' etc. – Luaan

6

Lesen Sie die Spezifikation für Layoutregeln sorgfältiger. Layoutregeln regeln nur das Layout, wenn das Objekt in nicht verwaltetem Speicher verfügbar gemacht wird. Dies bedeutet, dass der Compiler die Felder beliebig platzieren kann, bis das Objekt tatsächlich exportiert wird. Zu meiner Überraschung gilt dies sogar für FixedLayout!

Ian Ringrose ist recht Compiler Effizienzprobleme, und das tut Konto für das endgültige Layout, das hier ausgewählt wird, aber es hat nichts mit dem zu tun, warum der Compiler Ihre Layout-Spezifikation ignoriert.

Ein paar Leute haben darauf hingewiesen, dass DateTime Auto-Layout hat. Das ist die ultimative Quelle Ihrer Überraschung, aber der Grund ist ein wenig unklar. Die Dokumentation für automatisches Layout besagt, dass Objekte, die mit [Auto] -Layout definiert wurden, nicht außerhalb von verwaltetem Code angezeigt werden können. Wenn Sie dies versuchen, wird eine Ausnahme generiert. Beachten Sie auch, dass DateTime ein Werttyp ist. Durch die Integration eines Wertetyps mit automatischem Layout in Ihre Struktur haben Sie versehentlich versprochen, dass Sie die Struktur mit der Struktur nicht dem verwalteten Code aussetzen würden (da dies die DateTime offen legen würde und eine Ausnahme erzeugen würde). Da die Layoutregeln nur Objekte in nicht verwaltetem Speicher steuern und Ihr Objekt niemals nicht verwaltetem Speicher ausgesetzt werden kann, ist der Compiler bei der Wahl des Layouts nicht eingeschränkt und kann frei tun, was er will. In diesem Fall wird auf die Auto-Layout-Richtlinie zurückgegriffen, um eine bessere Strukturierung und Ausrichtung zu erreichen.

Dort! War das nicht offensichtlich?

All dies ist übrigens bei statischer Kompilierzeit erkennbar. In der Tat ist der Compiler es zu erkennen, um zu entscheiden, dass es Ihre Layout-Anweisung ignorieren kann. Wenn man es erkannt hat, scheint eine Warnung des Compilers in Ordnung zu sein. Sie haben eigentlich nichts falsch gemacht, aber es ist hilfreich, wenn Sie etwas geschrieben haben, das keine Wirkung hat. Die verschiedenen Kommentare, die hier empfohlen werden Fixed Layout sind im Allgemeinen gute Ratschläge, aber in diesem Fall hätte das nicht unbedingt einen Effekt, da das Compiler durch das DateTime-Feld davon ausgenommen war, Layout überhaupt zu honorieren. Schlimmer noch: der Compiler ist nicht erforderlich zu Ehren Layout, aber es ist frei zu Ehren Layout. Das bedeutet, dass aufeinanderfolgende Versionen von CLR sich darin unterschiedlich verhalten können.

Die Behandlung von Layout ist meiner Ansicht nach ein Konstruktionsfehler in CLI. Wenn der Benutzer ein Layout angibt, sollte der Compiler nicht um sie herum gehen. Es ist besser, die Dinge einfach zu halten und den Compiler tun zu lassen, was er gesagt hat. Vor allem, wenn es um das Layout geht. "Clever", wie wir alle wissen, ist ein Wort mit vier Buchstaben.

+1

Kein Konstruktionsfehler in CLI IMO. Es soll das Layout der verwalteten Struktur überhaupt nicht ändern - das ist nur eine Leistungsoptimierung, die es dem Marshaller in einigen Fällen erlaubt, das Kopieren der Struktur zu vermeiden. Sie legen das Layout der Struktur nicht fest, Sie weisen * den Marshaller * an, die Struktur auf eine bestimmte Weise zu marshallen - und es wird * so marshalled (in diesem Fall verursacht eine Ausnahme). Dies ist explizit in https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute(v=vs.110).aspx#Anchor_7 dokumentiert – Luaan