Ich respektiere die Kapselung nicht oder verwende keine andere Art von Methodentabelle, um schnell private Methoden aufzurufen

Hallo an alle. Ich möchte ein Beispiel für die Verwendung von StructLayout für etwas Interessanteres als Beispiele mit Bytes, Ints und anderen Zahlen vorstellen, bei denen alles etwas mehr als erwartet geschieht.

Bevor Sie sich auf eine blitzschnelle Kapselungsverletzung einlassen, sollten Sie sich kurz und bündig daran erinnern, was StructLayout ist. Genau genommen ist dies sogar ein StructLayoutAttribute, dh ein Attribut, mit dem Sie Strukturen und Klassen erstellen können, die der Vereinigung in C ++ ähneln. Mit diesem Attribut können Sie die Platzierung von Klassenmitgliedern im Speicher steuern. Dementsprechend wird es über der Klasse platziert. Wenn eine Klasse zwei Felder hat, erwarten wir normalerweise, dass diese nacheinander angeordnet sind, dh sie sind unabhängig voneinander (nicht überlappen). Mit StructLayout kann jedoch festgelegt werden, dass die Position der Felder nicht von der Umgebung, sondern vom Benutzer bestimmt wird. Verwenden Sie den Parameter LayoutKind.Explicit, um Feldversätze explizit anzugeben. Um anzugeben, bei welchem ​​Versatz relativ zum Anfang der Klasse / Struktur (im Folgenden als Klasse bezeichnet) das Feld platziert werden soll, sollten wir das FieldOffset-Attribut darüber setzen, das als Parameter die Anzahl der Bytes verwendet - den Einzug vom Anfang der Klasse. Ein negativer Wert kann nicht übergeben werden. Denken Sie also nicht einmal daran, Zeiger auf eine Methodentabelle oder einen Index eines Synchronisationsblocks zu verderben. Dies ist etwas komplizierter.

Beginnen wir mit dem Schreiben des Codes. Zunächst schlage ich vor, mit einem einfachen Beispiel zu beginnen. Erstellen Sie eine Klasse mit folgendem Formular:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); } 

Als nächstes verwenden wir den obigen Mechanismus, um Feldversätze explizit festzulegen.
 [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; } 

Im Moment werde ich die Erklärungen verschieben und die schriftliche Klasse wie folgt verwenden:
  class Program { static void Main(string[] args) { CustomStructWithLayout instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClass(); instance.Str = "4564"; Console.WriteLine(instance.SomeInstance.GetType()); //System.String Console.WriteLine(instance.SomeInstance.ToString()); //4564 Console.Read(); } } 

Insgesamt Wenn Sie die GetType () -Methode aufrufen, wird eine Zeichenfolge zurückgegeben. Die ToString () -Methode ist ungezogen und gibt uns die Zeichenfolge "4564".
Entladung für das Gehirn: Was wird angezeigt, wenn die virtuelle Eigenschaft CustomClass aufgerufen wird?

Wie Sie vielleicht vermutet haben, haben wir CustomStructWithLayout initialisiert, beide Links sind null, dann initialisieren wir ein Feld unseres Typs und weisen danach dem Str-Feld eine Zeichenfolge zu. Infolgedessen bleibt von CustomClass etwas mehr übrig als nichts. Darüber befand sich eine Zeile mit ihrer gesamten internen Struktur, einschließlich einer Methodentabelle und eines Index eines Synchronisationsblocks. Der Compiler sieht jedoch, dass das Feld immer noch vom Typ unserer Klasse ist.
Zum Beweis werde ich einen kleinen Ausschnitt aus WinDbg geben:

Hier können Sie einige ungewöhnliche Dinge sehen. Das erste ist, dass im Objekt die Adressen in den Methodentabellen erwartungsgemäß unterschiedliche Felder für die Klassenfelder haben, die Adresse des Feldwerts jedoch eins ist. Das zweite - Sie können sehen, dass sich beide Felder am Versatz 4 befinden. Ich denke, die meisten werden es verstehen, aber nur für den Fall, dass ich es erklären werde, gibt es direkt an der Adresse des Objekts einen Link zur Methodentabelle. Felder beginnen mit einem Versatz von 4 Bytes (für z 32 Bit), und der Index des Synchronisationsblocks befindet sich mit einem Versatz von -4.

Nachdem Sie herausgefunden haben, was los ist, können Sie versuchen, mithilfe von Offsets etwas aufzurufen, das Sie nicht anrufen sollten.
Dazu habe ich die Struktur der String-Klasse in einer meiner Klassen wiederholt. Es stimmt, ich habe nur den Anfang wiederholt, da die String-Klasse sehr umfangreich ist.
  public class CustomClassLikeString { public const int FakeAlignConst = 3; public const int FakeCharPtrAlignConst = 3; public static readonly object FakeStringEmpty; public char FakeFirstChar; public int FakeLength = 3; public const int FakeTrimBoth = 3; public const int FakeTrimHead = 3; public const int FakeTrimTail = 3; public CustomClassLikeString(){} public CustomClassLikeString(int a){} public CustomClassLikeString(byte a){} public CustomClassLikeString(short a){} public CustomClassLikeString(string a){} public CustomClassLikeString(uint a){} public CustomClassLikeString(ushort a){} public CustomClassLikeString(long a){ } public void Stub1(){} public virtual int CompareTo(object value) { return 800; } public virtual int CompareTo(string value) { return 801; } } 

Nun, die Struktur mit Layout ändert sich ein wenig
  [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; } 

Wenn Sie FakeLength oder die CompareTo () -Methode aufrufen, wird aufgrund des identischen Versatzes dieser Klassenmitglieder relativ zur Adresse des Objekts selbst die entsprechende Zeichenfolgenmethode (in diesem Fall) aufgerufen. Es hat eine ganze Weile gedauert, bis ich die erste private Methode in der Zeile gefunden habe, die ich verwenden kann, also habe ich mich für die öffentliche entschieden. Aber das Feld ist privat, alles ist fair. Übrigens werden die Methoden virtuell gestaltet, um vor allen Arten von Optimierungen zu schützen, die die Arbeit beeinträchtigen (z. B. Einbetten), und um die Methode durch einen Offset in der Methodentabelle aufzurufen.

Also Leistung. Es ist eine Tatsache, dass ein direkter Konkurrent bei der Herausforderung dessen, was nicht notwendig ist, um zu verursachen, und bei der Verletzung der Kapselung Reflexion ist. Ich denke, es ist bereits klar, dass wir schneller als dieses Ding sind. Wir alle analysieren keine Metadaten. Genaue Werte:
MethodeJobMittelwertFehlerStddevMedian
StructLayoutFieldClr0,0597 ns0,0344 ns0,0396 ns0,0498 ns
ReflectionFieldClr197,1257 ns1,9148 ns1,7911 ns197,4777 ns
StructLayoutMethodClr3,5195 ns0,0382 ns0,0319 ns3,5285 ns
ReflectionMethodClr743,9793 ns13.7378 ns12.8504 ns743,8471 ns
Hier ist ein langer Code, wie ich die Leistung gemessen habe (wenn jemand sie benötigt):
Code
  [ClrJob] [RPlotExporter, RankColumn] [InProcessAttribute] public class Benchmarking { private CustomStructWithLayout instance; private string str; [GlobalSetup] public void Setup() { instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClassLikeString(); instance.Str = "4564"; str = "4564"; } [Benchmark] public int StructLayoutField() { return instance.SomeInstance.FakeLength; } [Benchmark] public int ReflectionField() { return (int)typeof(string).GetField("m_stringLength", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(str); } [Benchmark] public int StructLayoutMethod() { return instance.SomeInstance.CompareTo("4564"); } [Benchmark] public int ReflectionMethod() { return (int)typeof(string).GetMethod("CompareTo", new[] { typeof(string) }).Invoke(str, new[] { "4564" }); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } } 

Source: https://habr.com/ru/post/de423657/


All Articles